From 8301d2838cc166bbf3fc6f62803310b1191a48cc Mon Sep 17 00:00:00 2001 From: Jennifer Hooper Date: Thu, 17 Aug 2017 13:13:57 -0700 Subject: [PATCH 01/23] Update link to community newsletter --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5b252a0..035bba10 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The API reference is located at [realm.io/docs/javscript/latest/api](https://rea - **Need help with your code?**: Look for previous questions on the [#realm tag](https://stackoverflow.com/questions/tagged/realm?sort=newest) — or [ask a new question](https://stackoverflow.com/questions/ask?tags=realm). We actively monitor and answer questions on SO! - **Have a bug to report?** [Open an issue](https://github.com/realm/realm-js/issues/new). If possible, include the version of Realm, a full log, the Realm file, and a project that shows the issue. - **Have a feature request?** [Open an issue](https://github.com/realm/realm-js/issues/new). Tell us what the feature should do, and why you want the feature. -- Sign up for our [**Community Newsletter**](https://www2.realm.io/l/210132/2016-12-05/fy9m) to get regular tips, learn about other use-cases and get alerted of blog posts and tutorials about Realm. +- Sign up for our [**Community Newsletter**](https://go.pardot.com/l/210132/2017-04-26/3j74l) to get regular tips, learn about other use-cases and get alerted of blog posts and tutorials about Realm. ## Building Realm From 0c557fcfe5e3ff069c2b0c397827d4aa12d70c24 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 18 Aug 2017 14:19:56 +0200 Subject: [PATCH 02/23] How to contribute a function (#1224) * Guideline on how to add a function --- CONTRIBUTING.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f49b825e..bbc9d06b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,3 +38,46 @@ Below are some guidelines about the format of the commit message itself: Realm welcomes all contributions! The only requirement we have is that, like many other projects, we need to have a [Contributor License Agreement](https://en.wikipedia.org/wiki/Contributor_License_Agreement) (CLA) in place before we can accept any external code. Our own CLA is a modified version of the Apache Software Foundation’s CLA. [Please submit your CLA electronically using our Google form](https://docs.google.com/forms/d/1bVp-Wp5nmNFz9Nx-ngTmYBVWVdwTyKj4T0WtfVm0Ozs/viewform?fbzx=4154977190905366979) so we can accept your submissions. The GitHub username you file there will need to match that of your Pull Requests. If you have any questions or cannot file the CLA electronically, you can email . + +### Guidelines + +Adding new functionality to Realm JavaScript requires that you modify a few places in the repository. As an example, consider adding a function `crashOnStart()` to the class `Realm`. The subsections below guides you through where and what to add. + +#### Add the function + +First, add a prototype of function to `src/js_realm.hpp`; look for a section marked by the comment `// method`. The prototype looks like: + +``` +static void crashOnStart(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); +``` + +You have to implement the function. Find a place in `src/js_realm.hpp` to add it (maybe at the end): + +``` +template +void RealmClass::crashOnStart(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 0); // <- the function doesn't take any arguments + + SharedRealm realm = *get_internal>(this_object); // <- unwrap the Realm instance + + // add the actual implement ... +} +``` + +Testing is important, and in `tests/js/realm-tests.js` you can add the tests you need. + +#### Wrap the function + +In order to call the C++ implementation, the JavaScript engine has to know about the function. You must simply add it to the map of methods/functions. Find `MethodMap const methods` declaration in `src/js_realm.hpp` and add your function to it: + +``` +{"crashOnStart", wrap}, +``` + +#### The final details + +To finish adding your new function, you will have to add your function a few places: + +* In `lib/index.d.ts` you add the TypeScript declaration +* Documentation is added in `docs/realm.js` +* Add your function to `lib/browser/index.js` in order to enable it in the Chrome Debugger \ No newline at end of file From bd28c059369872d4eda0cae86c925125d8491aad Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 18 Aug 2017 14:22:29 +0200 Subject: [PATCH 03/23] Add shouldCompactOnLaunch option to configuration (#1209) * Adding shouldCompactOnLaunch option to configuration * Adding Realm.compact() --- CHANGELOG.md | 13 ++++++++++ docs/realm.js | 25 ++++++++++++++++++ lib/browser/index.js | 1 + lib/index.d.ts | 6 +++++ src/js_realm.hpp | 37 +++++++++++++++++++++++++++ tests/js/realm-tests.js | 56 +++++++++++++++++++++++++++++++++++++++++ tests/js/schemas.js | 7 ++++++ 7 files changed, 145 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59422ec7..1cde8d66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,16 @@ +X.Y.Z Release notes +============================================================= +### Breaking changes +* None + +### Enhancements +* Added `shouldCompactOnLaunch` to configuration (#507). +* Added `Realm.compact()` for manually compacting Realm files. + +### Bug fixes +* None + + 1.10.3 Release notes (2017-8-16) ============================================================= ### Breaking changes diff --git a/docs/realm.js b/docs/realm.js index 0d7b5fca..3f5fbd99 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -182,6 +182,24 @@ class Realm { * @param {function()} callback */ write(callback) {} + + /** + * Replaces all string columns in this Realm with a string enumeration column and compacts the + * database file. + * + * Cannot be called from a write transaction. + * + * Compaction will not occur if other `Realm` instances exist. + * + * While compaction is in progress, attempts by other threads or processes to open the database will + * wait. + * + * Be warned that resource requirements for compaction is proportional to the amount of live data in + * the database. Compaction works by writing the database contents to a temporary database file and + * then replacing the database with the temporary one. + * @returns {true} if compaction succeeds. + */ + compact() {} } /** @@ -213,6 +231,13 @@ Realm.defaultPath; * This function takes two arguments: * - `oldRealm` - The Realm before migration is performed. * - `newRealm` - The Realm that uses the latest `schema`, which should be modified as necessary. + * @property {callback(number, number)} [shouldCompactOnLaunch] - The function called when opening + * a Realm for the first time during the life of a process to determine if it should be compacted + * before being returned to the user. The function takes two arguments: + * - `totalSize` - The total file size (data + free space) + * - `unusedSize` - The total bytes used by data in the file. + * It returns `true` to indicate that an attempt to compact the file should be made. The compaction + * will be skipped if another process is accessing it. * @property {string} [path={@link Realm.defaultPath}] - The path to the file where the * Realm database should be stored. * @property {boolean} [readOnly=false] - Specifies if this Realm should be opened as read-only. diff --git a/lib/browser/index.js b/lib/browser/index.js index 81b2a804..8f6ef684 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -132,6 +132,7 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [ 'delete', 'deleteAll', 'write', + 'compact', ], true); const Sync = { diff --git a/lib/index.d.ts b/lib/index.d.ts index 196b83d5..663fe621 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -77,6 +77,7 @@ declare namespace Realm { interface Configuration { encryptionKey?: ArrayBuffer | ArrayBufferView | Int8Array; migration?: (oldRealm: Realm, newRealm: Realm) => void; + shouldCompactOnLaunch?: (totalBytes: number, usedBytes: number) => boolean; path?: string; readOnly?: boolean; schema?: ObjectClass[] | ObjectSchema[]; @@ -452,6 +453,11 @@ declare class Realm { * @returns void */ write(callback: () => void): void; + + /** + * @returns boolean + */ + compact(): boolean; } declare module 'realm' { diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 2d9ac368..a54dfb7e 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -176,6 +176,8 @@ public: 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 &); + static void compact(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); + // properties static void get_empty(ContextType, ObjectType, ReturnValue &); @@ -223,6 +225,7 @@ public: {"removeListener", wrap}, {"removeAllListeners", wrap}, {"close", wrap}, + {"compact", wrap}, }; PropertyMap const properties = { @@ -390,6 +393,28 @@ void RealmClass::constructor(ContextType ctx, ObjectType this_object, size_t config.schema_version = 0; } + static const String compact_on_launch_string = "shouldCompactOnLaunch"; + ValueType compact_value = Object::get_property(ctx, object, compact_on_launch_string); + if (!Value::is_undefined(ctx, compact_value)) { + if (config.schema_mode == SchemaMode::ReadOnly) { + throw std::invalid_argument("Cannot set 'shouldCompactOnLaunch' when 'readOnly' is set."); + } + if (config.sync_config) { + throw std::invalid_argument("Cannot set 'shouldCompactOnLaunch' when 'sync' is set."); + } + + FunctionType should_compact_on_launch_function = Value::validated_to_function(ctx, compact_value, "shouldCompactOnLaunch"); + config.should_compact_on_launch_function = [=](uint64_t total_bytes, uint64_t unused_bytes) { + ValueType arguments[2] = { + Value::from_number(ctx, total_bytes), + Value::from_number(ctx, unused_bytes) + }; + + ValueType should_compact = Function::callback(ctx, should_compact_on_launch_function, this_object, 2, arguments); + return Value::to_boolean(ctx, should_compact); + }; + } + static const String migration_string = "migration"; ValueType migration_value = Object::get_property(ctx, object, migration_string); if (!Value::is_undefined(ctx, migration_value)) { @@ -836,5 +861,17 @@ void RealmClass::close(ContextType ctx, FunctionType, ObjectType this_object, realm->close(); } +template +void RealmClass::compact(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); + if (realm->is_in_transaction()) { + throw std::runtime_error("Cannot compact a Realm within a transaction."); + } + + return_value.set(realm->compact()); +} + } // js } // realm diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index a2c335e0..67749dbd 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -943,5 +943,61 @@ module.exports = { realm.write(() => realm.delete(realm.objects('PersonObject'))); TestCase.assertTrue(realm.empty); + }, + + testCompact: function() { + var wasCalled = false; + const count = 1000; + // create compactable Realm + const realm1 = new Realm({schema: [schemas.StringOnly]}); + realm1.write(() => { + realm1.create('StringOnlyObject', { stringCol: 'A' }); + for (var i = 0; i < count; i++) { + realm1.create('StringOnlyObject', { stringCol: 'ABCDEFG' }); + } + realm1.create('StringOnlyObject', { stringCol: 'B' }); + }); + realm1.close(); + + // open Realm and see if it is compacted + var shouldCompact = function(totalBytes, usedBytes) { + wasCalled = true; + const fiveHundredKB = 500*1024; + return (totalBytes > fiveHundredKB) && (usedBytes / totalBytes) < 0.2; + }; + const realm2 = new Realm({schema: [schemas.StringOnly], shouldCompactOnLaunch: shouldCompact}); + TestCase.assertTrue(wasCalled); + TestCase.assertEqual(realm2.objects('StringOnlyObject').length, count + 2); + // we don't check if the file is smaller as we assume that Object Store does it + realm2.close(); + }, + + testManualCompact: function() { + const realm1 = new Realm({schema: [schemas.StringOnly]}); + realm1.write(() => { + realm1.create('StringOnlyObject', { stringCol: 'A' }); + }); + TestCase.assertTrue(realm1.compact()); + realm1.close(); + + const realm2 = new Realm({schema: [schemas.StringOnly]}); + TestCase.assertEqual(realm2.objects('StringOnlyObject').length, 1); + realm2.close(); + }, + + testManualCompactInWrite: function() { + const realm = new Realm({schema: [schemas.StringOnly]}); + realm.write(() => { + TestCase.assertThrows(() => { + realm.compact(); + }); + }); + TestCase.assertTrue(realm.empty); + }, + + testManualCompactMultipleInstances: function() { + const realm1 = new Realm({schema: [schemas.StringOnly]}); + const realm2 = new Realm({schema: [schemas.StringOnly]}); + TestCase.assertThrows(realm1.compact()); } }; diff --git a/tests/js/schemas.js b/tests/js/schemas.js index 214dfd5a..7c20f855 100644 --- a/tests/js/schemas.js +++ b/tests/js/schemas.js @@ -126,6 +126,13 @@ exports.StringPrimary = { } }; +exports.StringOnly = { + name: 'StringOnlyObject', + properties: { + stringCol: 'string', + } +}; + exports.AllTypes = { name: 'AllTypesObject', primaryKey: 'primaryCol', From 85fb49b35455880763cf7f2e1b5722db5c6871e9 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Mon, 21 Aug 2017 17:48:53 +0200 Subject: [PATCH 04/23] Adding methods and property to manually control write transactions (#1216) * Adding methods and property to manually control write transactions --- CHANGELOG.md | 5 +++-- docs/realm.js | 24 ++++++++++++++++++++++++ lib/browser/index.js | 4 ++++ lib/index.d.ts | 16 ++++++++++++++++ src/js_realm.hpp | 37 +++++++++++++++++++++++++++++++++++++ tests/js/realm-tests.js | 31 +++++++++++++++++++++++++++++++ 6 files changed, 115 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cde8d66..dab2431f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,14 @@ X.Y.Z Release notes * None ### Enhancements +* Added methods `Realm.beginTransaction()`, `Realm.commitTransaction()`, `Realm.cancelTransaction()` to manually control write transactions. +* Added property `Realm.isInTransaction` which indicates if write transaction is in progress. * Added `shouldCompactOnLaunch` to configuration (#507). * Added `Realm.compact()` for manually compacting Realm files. ### Bug fixes * None - 1.10.3 Release notes (2017-8-16) ============================================================= ### Breaking changes @@ -20,7 +21,7 @@ X.Y.Z Release notes * None ### Bug fixes -* none +* None 1.10.2 Release notes (2017-8-16) diff --git a/docs/realm.js b/docs/realm.js index 3f5fbd99..a4d667aa 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -64,6 +64,14 @@ class Realm { */ get schemaVersion() {} + /** + * Indicates if this Realm is in a write transaction. + * @type {boolean} + * @readonly + * @since 1.10.3 + */ + get isInTransaction() {} + /** * Gets the sync session if this is a synced Realm * @type {Session} @@ -184,6 +192,22 @@ class Realm { write(callback) {} /** + * Initiate a write transaction. + * @throws {Error} When already in write transaction + */ + beginTransaction() {} + + /** + * Commit a write transaction. + */ + commitTransaction() {} + + /** + * Cancel a write transaction. + */ + cancelTransaction() {} + + /* * Replaces all string columns in this Realm with a string enumeration column and compacts the * database file. * diff --git a/lib/browser/index.js b/lib/browser/index.js index 8f6ef684..ccecf45e 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -58,6 +58,7 @@ function setupRealm(realm, realmId) { 'schema', 'schemaVersion', 'syncSession', + 'isInTransaction', ].forEach((name) => { Object.defineProperty(realm, name, {get: util.getterForProperty(name)}); }); @@ -133,6 +134,9 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [ 'deleteAll', 'write', 'compact', + 'beginTransaction', + 'commitTransaction', + 'cancelTransaction', ], true); const Sync = { diff --git a/lib/index.d.ts b/lib/index.d.ts index 663fe621..227f82d5 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -358,6 +358,7 @@ declare class Realm { readonly readOnly: boolean; readonly schema: Realm.ObjectSchema[]; readonly schemaVersion: number; + readonly isInTransaction: boolean; readonly syncSession: Realm.Sync.Session | null; @@ -454,6 +455,21 @@ declare class Realm { */ write(callback: () => void): void; + /** + * @returns void + */ + beginTransaction(): void; + + /** + * @returns void + */ + commitTransaction(): void; + + /** + * @returns void + */ + cancelTransaction(): void; + /** * @returns boolean */ diff --git a/src/js_realm.hpp b/src/js_realm.hpp index a54dfb7e..c9702575 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -171,6 +171,9 @@ public: 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 begin_transaction(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue&); + static void commit_transaction(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue&); + static void cancel_transaction(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 &); @@ -185,6 +188,7 @@ public: static void get_schema_version(ContextType, ObjectType, ReturnValue &); static void get_schema(ContextType, ObjectType, ReturnValue &); static void get_read_only(ContextType, ObjectType, ReturnValue &); + static void get_is_in_transaction(ContextType, ObjectType, ReturnValue &); #if REALM_ENABLE_SYNC static void get_sync_session(ContextType, ObjectType, ReturnValue &); #endif @@ -221,6 +225,9 @@ public: {"delete", wrap}, {"deleteAll", wrap}, {"write", wrap}, + {"beginTransaction", wrap}, + {"commitTransaction", wrap}, + {"cancelTransaction", wrap}, {"addListener", wrap}, {"removeListener", wrap}, {"removeAllListeners", wrap}, @@ -234,6 +241,7 @@ public: {"schemaVersion", {wrap, nullptr}}, {"schema", {wrap, nullptr}}, {"readOnly", {wrap, nullptr}}, + {"isInTransaction", {wrap, nullptr}}, #if REALM_ENABLE_SYNC {"syncSession", {wrap, nullptr}}, #endif @@ -558,6 +566,11 @@ void RealmClass::get_read_only(ContextType ctx, ObjectType object, ReturnValu return_value.set(get_internal>(object)->get()->config().read_only()); } +template +void RealmClass::get_is_in_transaction(ContextType ctx, ObjectType object, ReturnValue &return_value) { + return_value.set(get_internal>(object)->get()->is_in_transaction()); +} + #if REALM_ENABLE_SYNC template void RealmClass::get_sync_session(ContextType ctx, ObjectType object, ReturnValue &return_value) { @@ -811,6 +824,30 @@ void RealmClass::write(ContextType ctx, FunctionType, ObjectType this_object, realm->commit_transaction(); } +template +void RealmClass::begin_transaction(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); + realm->begin_transaction(); +} + +template +void RealmClass::commit_transaction(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); + realm->commit_transaction(); +} + +template +void RealmClass::cancel_transaction(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); + realm->cancel_transaction(); +} + template void RealmClass::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 2); diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 67749dbd..4a66bc5c 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -945,6 +945,37 @@ module.exports = { TestCase.assertTrue(realm.empty); }, + testManualTransaction: function() { + const realm = new Realm({schema: [schemas.TestObject]}); + TestCase.assertTrue(realm.empty); + + realm.beginTransaction(); + realm.create('TestObject', {doubleCol: 3.1415}); + realm.commitTransaction(); + + TestCase.assertEqual(realm.objects('TestObject').length, 1); + }, + + testCancelTransaction: function() { + const realm = new Realm({schema: [schemas.TestObject]}); + TestCase.assertTrue(realm.empty); + + realm.beginTransaction(); + realm.create('TestObject', {doubleCol: 3.1415}); + realm.cancelTransaction(); + + TestCase.assertTrue(realm.empty); + }, + + testIsInTransaction: function() { + const realm = new Realm({schema: [schemas.TestObject]}); + TestCase.assertTrue(!realm.isInTransaction); + realm.beginTransaction(); + TestCase.assertTrue(realm.isInTransaction); + realm.cancelTransaction(); + TestCase.assertTrue(!realm.isInTransaction); + }, + testCompact: function() { var wasCalled = false; const count = 1000; From a3795fdfb052258492b80c76ec9572e263c322fb Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Fri, 18 Aug 2017 13:49:32 +0530 Subject: [PATCH 05/23] Check if the supported version (10e) of Android NDK is installed --- scripts/check-environment.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/scripts/check-environment.js b/scripts/check-environment.js index b41a4cfa..f01ff449 100644 --- a/scripts/check-environment.js +++ b/scripts/check-environment.js @@ -54,6 +54,29 @@ exec('npm --version', (err, stdout) => { validateEnvPath('REALM_CORE_PREFIX'); validateEnvPath('REALM_SYNC_PREFIX'); - // TODO: Check ANDROID_NDK and SDK for Android, and XCode for iOS. + // Check ANDROID_NDK version + exec('which ndk-build', (err, stdout) => { + if(err) { + console.error("Android NDK (ndk-build) not found"); + console.error(err.message); + process.exit(-1); + } + const ndkBuildPath = stdout.trim(); + const ndkDirPath = path.dirname(ndkBuildPath); + const releaseTxtPath = path.join(ndkDirPath, "RELEASE.TXT"); + + exec(`grep ^r10e ${releaseTxtPath}`, (err, stdout) => { + if(err) { + // RELEASE.TXT doesn't exist or version mismatch + console.error("Incompatible Android NDK version found. NDK 10e is required.") + process.exit(-1); + } + + successLog('Android NDK 10e found'); + }); + }); + + // TODO: Check SDK for Android, and XCode for iOS. + }); From a9254a1a2377f3dbd06a1be2880912487effcae0 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Fri, 18 Aug 2017 11:37:04 +0530 Subject: [PATCH 06/23] Use exact Node & NDK versions known to work in prerequisites --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 035bba10..8bcf7ecd 100644 --- a/README.md +++ b/README.md @@ -33,10 +33,10 @@ The API reference is located at [realm.io/docs/javscript/latest/api](https://rea In case you don't want to use the precompiled version on npm, you can build Realm yourself from source. You’ll need an Internet connection the first time you build in order to download the core library. Prerequisites: -- Node 4.0+ +- Node: 4.0 <= version < 7.0 - Xcode 7.2+ - Android SDK 23+ -- Android NDK 10e+ +- Android NDK 10e First clone this repository: From 9ab5468d1fa13bb7a48b40f28db887c25779eee1 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Tue, 22 Aug 2017 15:49:32 +0530 Subject: [PATCH 07/23] Revert "Check if the supported version (10e) of Android NDK is installed" This reverts commit a3795fdfb052258492b80c76ec9572e263c322fb. --- scripts/check-environment.js | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/scripts/check-environment.js b/scripts/check-environment.js index f01ff449..b41a4cfa 100644 --- a/scripts/check-environment.js +++ b/scripts/check-environment.js @@ -54,29 +54,6 @@ exec('npm --version', (err, stdout) => { validateEnvPath('REALM_CORE_PREFIX'); validateEnvPath('REALM_SYNC_PREFIX'); - // Check ANDROID_NDK version - exec('which ndk-build', (err, stdout) => { - if(err) { - console.error("Android NDK (ndk-build) not found"); - console.error(err.message); - process.exit(-1); - } + // TODO: Check ANDROID_NDK and SDK for Android, and XCode for iOS. - const ndkBuildPath = stdout.trim(); - const ndkDirPath = path.dirname(ndkBuildPath); - const releaseTxtPath = path.join(ndkDirPath, "RELEASE.TXT"); - - exec(`grep ^r10e ${releaseTxtPath}`, (err, stdout) => { - if(err) { - // RELEASE.TXT doesn't exist or version mismatch - console.error("Incompatible Android NDK version found. NDK 10e is required.") - process.exit(-1); - } - - successLog('Android NDK 10e found'); - }); - }); - - // TODO: Check SDK for Android, and XCode for iOS. - }); From 15f60284deca177b5fcce3c2fcbceaf7c271ac21 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Wed, 23 Aug 2017 13:06:22 +0530 Subject: [PATCH 08/23] Fix 'jni_md.h: No such file or directory' error when building on Ubuntu 16.04 LTS When building Android on Ubuntu 16.04 LTS, this fixes an error for a missing header file: In file included from ./src/android/io_realm_react_RealmReactModule.cpp:19:0: /usr/lib/jvm/java-8-oracle/include/jni.h:45:20: fatal error: jni_md.h: No such file or directory ^ compilation terminated. make: *** [/home/ashwinp/projects/realm-js/react-native/android/build/tmp/buildReactNdkLib/local/armeabi-v7a/objs-debug/realmreact/src/android/io_realm_react_RealmReactModule.o] Error 1 make: *** Waiting for unfinished jobs.... make: Leaving directory `/home/ashwinp/projects/realm-js/react-native/android/src/main/jni' :buildReactNdkLib FAILED FAILURE: Build failed with an exception. --- react-native/android/src/main/jni/Android.mk | 1 + 1 file changed, 1 insertion(+) diff --git a/react-native/android/src/main/jni/Android.mk b/react-native/android/src/main/jni/Android.mk index 1fe4eafa..9fe404aa 100644 --- a/react-native/android/src/main/jni/Android.mk +++ b/react-native/android/src/main/jni/Android.mk @@ -70,6 +70,7 @@ LOCAL_C_INCLUDES += src/object-store/external/pegtl LOCAL_C_INCLUDES += vendor LOCAL_C_INCLUDES += $(JAVA_HOME)/include LOCAL_C_INCLUDES += $(JAVA_HOME)/include/darwin +LOCAL_C_INCLUDES += $(JAVA_HOME)/include/linux LOCAL_C_INCLUDES += core/include ifeq ($(strip $(BUILD_TYPE_SYNC)),1) LOCAL_C_INCLUDES += src/object-store/src/sync From 5547df33290676370d250ed4b9433de37a26f558 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Thu, 24 Aug 2017 16:04:47 +0530 Subject: [PATCH 09/23] Check that Android NDK version 10e is being used for the build --- react-native/android/build.gradle | 38 ++++++++++++++++++++++++++ react-native/android/gradle.properties | 1 + 2 files changed, 39 insertions(+) diff --git a/react-native/android/build.gradle b/react-native/android/build.gradle index 01052378..53fceccc 100644 --- a/react-native/android/build.gradle +++ b/react-native/android/build.gradle @@ -137,6 +137,41 @@ def findNdkBuildFullPath() { return null } +def checkNdkVersion(ndkBuildFullPath) { + def ndkPath = new File(ndkBuildFullPath).getParent() + def detectedNdkVersion + def releaseFile = new File(ndkPath, 'RELEASE.TXT') + def propertyFile = new File(ndkPath, 'source.properties') + if (releaseFile.isFile()) { + detectedNdkVersion = releaseFile.text.trim().split()[0].split('-')[0] + } else if (propertyFile.isFile()) { + detectedNdkVersion = getValueFromPropertiesFile(propertyFile, 'Pkg.Revision') + if (detectedNdkVersion == null) { + throw new GradleException("Failed to obtain the NDK version information from ${ndkPath}/source.properties") + } + } else { + throw new GradleException("Neither ${releaseFile.getAbsolutePath()} nor ${propertyFile.getAbsolutePath()} is a file.") + } + if (detectedNdkVersion != project.ndkVersion) { + throw new GradleException("Your NDK version: ${detectedNdkVersion}." + + " Realm JNI must be compiled with the version ${project.ndkVersion} of NDK.") + } +} + +static def getValueFromPropertiesFile(File propFile, String key) { + if (!propFile.isFile() || !propFile.canRead()) { + return null + } + def prop = new Properties() + def reader = propFile.newReader() + try { + prop.load(reader) + } finally { + reader.close() + } + return prop.get(key) +} + def getNdkBuildFullPath() { def ndkBuildFullPath = findNdkBuildFullPath() if (ndkBuildFullPath == null) { @@ -153,6 +188,9 @@ def getNdkBuildFullPath() { "(On Windows, make sure you escape backslashes in local.properties or use forward slashes, e.g. C:\\\\ndk or C:/ndk rather than C:\\ndk)", null) } + + checkNdkVersion(ndkBuildFullPath); + return ndkBuildFullPath } diff --git a/react-native/android/gradle.properties b/react-native/android/gradle.properties index 645750a5..9194dd13 100644 --- a/react-native/android/gradle.properties +++ b/react-native/android/gradle.properties @@ -6,3 +6,4 @@ POM_PACKAGING=aar POM_DESCRIPTION=Android Realm React Native module. Realm is a mobile database: a replacement for SQLite & ORMs DEBUG_BUILD=true android.useDeprecatedNdk=true +ndkVersion=r10e From a84f1ed75deeea2b589c4d0bbb02535528eb95eb Mon Sep 17 00:00:00 2001 From: "Constantin S. Pan" Date: Tue, 29 Aug 2017 10:37:29 +0200 Subject: [PATCH 10/23] Allow all log levels in Realm.Sync.setLogLevels() --- lib/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index 227f82d5..b9d7a624 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -314,7 +314,7 @@ declare namespace Realm.Sync { function addListener(serverURL: string, adminUser: Realm.Sync.User, regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): void; function removeAllListeners(name?: string): void; function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): void; - function setLogLevel(logLevel: 'error' | 'info' | 'debug'): void; + function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void; function setAccessToken(accessToken: string): void; type Instruction = { From a407b439449319b2614d1bdcabfe8404a4b68dfd Mon Sep 17 00:00:00 2001 From: agerson Date: Tue, 29 Aug 2017 04:51:36 -0400 Subject: [PATCH 11/23] Fixing #1191 - prop-types depreciation warning (#1250) Added prop-types package modified listview.js to use it to remove depreciation warning. --- package.json | 1 + react-native/listview.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 04d5d1b3..cbf7353a 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "nan": "^2.3.3", "node-fetch": "^1.6.3", "node-pre-gyp": "^0.6.30", + "prop-types": "^15.5.10", "request": "^2.78.0", "sync-request": "^3.0.1", "url-parse": "^1.1.7" diff --git a/react-native/listview.js b/react-native/listview.js index 96d64c3a..1c3dcec1 100644 --- a/react-native/listview.js +++ b/react-native/listview.js @@ -19,6 +19,7 @@ 'use strict'; import React from 'react'; +import PropTypes from 'prop-types'; import { ListView as BaseListView } from 'react-native'; function hashObjects(array) { @@ -196,8 +197,8 @@ export default class ListView extends React.Component { } ListView.propTypes = { - dataSource: React.PropTypes.instanceOf(ListViewDataSource).isRequired, - renderRow: React.PropTypes.func.isRequired, + dataSource: PropTypes.instanceOf(ListViewDataSource).isRequired, + renderRow: PropTypes.func.isRequired, }; ListView.DataSource = ListViewDataSource; From 402bf48f88700115ce8bb918373317cfddbd8960 Mon Sep 17 00:00:00 2001 From: Kristian Dupont Date: Tue, 29 Aug 2017 15:23:22 +0200 Subject: [PATCH 12/23] Permissions api (#1244) * Add basic permissions skeleton * ... * Update permissions api * Wait for server to process management realm, add offer api * Fix test of apply and get permissions, add offer test * Accept permission offer * Accept permission offer test * Invalidate permission offer * Add basic docs (still need some links etc) * Refactor tests * Only run permission tests where sync is enabled * Use legal syntax for user name generation * Add changelog entry * Streamline permission tests * Fix casing for access level names etc. * Add basic definitions to index.d.ts * Use settimeout for resolving promise from listener * Complete typescript defs * Improve docs * Allow 'any' as default recipient in getGrantedPermissions * Fix getSpecialPurposeRealm on iOS * Response to PR comments * Respond to PR comments * Fix offer description * Skip permission tests in chrome debugger :-/ --- CHANGELOG.md | 1 + docs/sync.js | 54 +++++ lib/index.d.ts | 41 ++++ lib/management-schema.js | 2 + lib/permission-api.js | 274 ++++++++++++++++++++++++++ lib/user-methods.js | 20 +- tests/js/index.js | 6 + tests/js/permission-tests.js | 115 +++++++++++ tests/react-test-app/index.android.js | 2 +- 9 files changed, 508 insertions(+), 7 deletions(-) create mode 100644 lib/permission-api.js create mode 100644 tests/js/permission-tests.js diff --git a/CHANGELOG.md b/CHANGELOG.md index dab2431f..fa26cea6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ X.Y.Z Release notes * Added property `Realm.isInTransaction` which indicates if write transaction is in progress. * Added `shouldCompactOnLaunch` to configuration (#507). * Added `Realm.compact()` for manually compacting Realm files. +* Added various methods for permission management (#1204). ### Bug fixes * None diff --git a/docs/sync.js b/docs/sync.js index 770d8583..b8137ef0 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -242,6 +242,60 @@ class User { * } */ retrieveAccount(provider, username) {} + + /** + * Asynchronously retrieves all permissions associated with the user calling this method. + * @param {string} recipient the optional recipient of the permission. Can be either + * 'any' which is the default, or 'currentUser' or 'otherUser' if you want only permissions + * belonging to the user or *not* belonging to the user. + * @returns {Results} a queryable collection of permission objects that provides detailed + * information regarding the granted access. + * The collection is a live query similar to what you would get by callig Realm.objects, + * so the same features apply - you can listen for notifications or filter it. + */ + getGrantedPermissions(recipient) { } + + /** + * Changes the permissions of a Realm. + * @param {object} condition - A condition that will be used to match existing users against. + * This should be an object, containing either the key 'userId', or 'metadataKey' and 'metadataValue'. + * @param {string} realmUrl - The path to the Realm that you want to apply permissions to. + * @param {string} accessLevel - The access level you want to set: 'none', 'read', 'write' or 'admin'. + * @returns {Promise} a Promise that, upon completion, indicates that the permissions have been + * successfully applied by the server. It will be resolved with the + * {@link PermissionChange PermissionChange} object that refers to the applied permission. + */ + applyPermissions(condition, realmUrl, accessLevel) { } + + /** + * Generates a token that can be used for sharing a Realm. + * @param {string} realmUrl - The Realm URL whose permissions settings should be changed. Use * to change + * the permissions of all Realms managed by this user. + * @param {string} accessLevel - The access level to grant matching users. Note that the access level + * setting is additive, i.e. you cannot revoke permissions for users who previously had a higher access level. + * Can be 'read', 'write' or 'admin'. + * @param {Date} [expiresAt] - Optional expiration date of the offer. If set to null, the offer doesn't expire. + * @returns {string} - A token that can be shared with another user, e.g. via email or message and then consumed by + * User.acceptPermissionOffer to obtain permissions to a Realm. + */ + offerPermissions(realmUrl, accessLevel, expiresAt) { } + + /** + * Consumes a token generated by {@link Realm#Sync#User#offerPermissions offerPermissions} to obtain permissions to a shared Realm. + * @param {string} token - The token, generated by User.offerPermissions + * @returns {string} The url of the Realm that the token has granted permissions to. + */ + acceptPermissionOffer(token) { } + + /** + * Invalidates a permission offer. + * Invalidating an offer prevents new users from consuming its token. It doesn't revoke any permissions that have + * already been granted. + * @param {string|PermissionOffer} permissionOfferOrToken - Either the token or the entire + * {@link PermissionOffer PermissionOffer} object that was generated with + * {@link Realm#Sync#User#offerPermissions offerPermissions}. + */ + invalidatePermissionOffer(permissionOfferOrToken) { } } /** diff --git a/lib/index.d.ts b/lib/index.d.ts index b9d7a624..4ecfd612 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -271,6 +271,47 @@ declare namespace Realm.Sync { logout(): void; openManagementRealm(): Realm; retrieveAccount(provider: string, username: string): Promise; + + getGrantedPermissions(recipient: 'any' | 'currentUser' | 'otherUser'): Results; + applyPermissions(condition: PermissionCondition, realmUrl: string, accessLevel: AccessLevel): Promise; + offerPermissions(realmUrl: string, accessLevel: AccessLevel, expiresAt?: Date): Promise; + acceptPermissionOffer(token: string): Promise + invalidatePermissionOffer(permissionOfferOrToken: PermissionOffer | string): Promise; + } + + type PermissionCondition = + { [object_type: string]: userId } | + { [object_type: string]: metadataKey, [object_type: string]: metadataValue }; + + type AccessLevel = 'none' | 'read' | 'write' | 'admin'; + + class PermissionChange { + id: string; + createdAt: Date; + updatedAt: Date; + statusCode?: number; + statusMessage?: string; + userId: string; + metadataKey?: string; + metadataValue?: string; + realmUrl: string; + mayRead?: boolean; + mayWrite?: boolean; + mayManage?: boolean; + } + + class PermissionOffer { + id: string; + createdAt: Date; + updatedAt: Date; + statusCode?: number; + statusMessage?: string; + token?: string; + realmUrl: string; + mayRead?: boolean; + mayWrite?: boolean; + mayManage?: boolean; + expiresAt?: Date; } interface SyncConfiguration { diff --git a/lib/management-schema.js b/lib/management-schema.js index d5975645..3d14b80e 100644 --- a/lib/management-schema.js +++ b/lib/management-schema.js @@ -28,6 +28,8 @@ module.exports = [ statusCode: { type: 'int', optional: true }, statusMessage: { type: 'string', optional: true }, userId: { type: 'string' }, + metadataKey: { type: 'string', optional: true }, + metadataValue: { type: 'string', optional: true }, realmUrl: { type: 'string' }, mayRead: { type: 'bool', optional: true }, mayWrite: { type: 'bool', optional: true }, diff --git a/lib/permission-api.js b/lib/permission-api.js new file mode 100644 index 00000000..4ff5b843 --- /dev/null +++ b/lib/permission-api.js @@ -0,0 +1,274 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 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 url_parse = require('url-parse'); +const managementSchema = require('./management-schema'); + +function generateUniqueId() { + const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + return uuid; +} + +const permissionSchema = [ + { + name: 'Permission', + properties: { + id: { type: 'string' }, + updatedAt: { type: 'date' }, + userId: { type: 'string' }, + path: { type: 'string' }, + mayRead: { type: 'bool' }, + mayWrite: { type: 'bool' }, + mayManage: { type: 'bool' }, + } + } +]; + +// Symbols are not supported on RN yet, so we use this for now: +const specialPurposeRealmsKey = '_specialPurposeRealms'; + +function getSpecialPurposeRealm(user, realmName, schema) { + if (!user.hasOwnProperty(specialPurposeRealmsKey)) { + user[specialPurposeRealmsKey] = {}; + } + + if (user[specialPurposeRealmsKey].hasOwnProperty(realmName)) { + return Promise.resolve(user[specialPurposeRealmsKey][realmName]); + } + + const url = url_parse(user.server); + if (url.protocol === 'http:') { + url.set('protocol', 'realm:'); + } else if (url.protocol === 'https:') { + url.set('protocol', 'realms:'); + } else { + throw new Error(`Unexpected user auth url: ${user.server}`); + } + + url.set('pathname', `/~/${realmName}`); + + const config = { + schema: schema, + sync: { + user, + url: url.href + } + }; + + const _Realm = user.constructor._realmConstructor; + + return new Promise((resolve, reject) => { + _Realm._waitForDownload(config, (error) => { + + // FIXME: I don't understand why, but removing the following setTimeout causes the subsequent + // setTimeout call (when resolving the promise) to hang on RN iOS. + // This might be related to our general makeCallback issue: #1255. + setTimeout(() => {}, 1); + + if (error) { + reject(error); + } + else { + try { + let syncedRealm = new _Realm(config); + user[specialPurposeRealmsKey][realmName] = syncedRealm; + //FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented (#1255) + setTimeout(() => resolve(syncedRealm), 1); + } catch (e) { + reject(e); + } + } + }); + }); +} + +function createInManagementRealm(user, modelName, modelInitializer) { + return getSpecialPurposeRealm(user, '__management', managementSchema) + .then(managementRealm => { + return new Promise((resolve, reject) => { + try { + let o; + + const listener = () => { + if (!o) { + return; + } + + const statusCode = o.statusCode; + if (typeof statusCode === 'number') { + managementRealm.removeListener('change', listener); + + if (statusCode === 0) { + setTimeout(() => resolve(o), 1); + } + else { + const e = new Error(o.statusMessage); + e.statusCode = statusCode; + e.managementObject = o; + setTimeout(() => reject(e), 1); + } + } + } + + managementRealm.addListener('change', listener); + + managementRealm.write(() => { + o = managementRealm.create(modelName, modelInitializer); + }); + } + catch (e) { + reject(e); + } + }); + }); +} + +const accessLevels = ['none', 'read', 'write', 'admin']; +const offerAccessLevels = ['read', 'write', 'admin']; + +module.exports = { + getGrantedPermissions(recipient) { + if (recipient && ['currentUser', 'otherUser', 'any'].indexOf(recipient) === -1) { + return Promise.reject(new Error(`'${recipient}' is not a valid recipient type. Must be 'any', 'currentUser' or 'otherUser'.`)); + } + + return getSpecialPurposeRealm(this, '__permission', permissionSchema) + .then(permissionRealm => { + let permissions = permissionRealm.objects('Permission') + .filtered('NOT path ENDSWITH "__permission" AND NOT path ENDSWITH "__management"'); + + if (recipient === 'currentUser') { + permissions = permissions.filtered('userId = $0', this.identity); + } + else if (recipient === 'otherUser') { + permissions = permissions.filtered('userId != $0', this.identity); + } + return permissions; + }); + }, + + applyPermissions(condition, realmUrl, accessLevel) { + if (!realmUrl) { + return Promise.reject(new Error('realmUrl must be specified')); + } + + if (accessLevels.indexOf(accessLevel) === -1) { + return Promise.reject(new Error(`'${accessLevel}' is not a valid access level. Must be ${accessLevels.join(', ')}.`)); + } + + const mayRead = accessLevel === 'read' || accessLevel === 'write' || accessLevel === 'admin'; + const mayWrite = accessLevel === 'write' || accessLevel === 'admin'; + const mayManage = accessLevel === 'admin'; + + const permissionChange = { + id: generateUniqueId(), + createdAt: new Date(), + updatedAt: new Date(), + realmUrl, + mayRead, + mayWrite, + mayManage + }; + + if (condition.hasOwnProperty('userId')) { + permissionChange.userId = condition.userId; + } + else { + permissionChange.userId = ''; + permissionChange.metadataKey = condition.metadataKey; + permissionChange.metadataValue = condition.metadataValue; + } + + return createInManagementRealm(this, 'PermissionChange', permissionChange); + }, + + offerPermissions(realmUrl, accessLevel, expiresAt) { + if (!realmUrl) { + return Promise.reject(new Error('realmUrl must be specified')); + } + + if (offerAccessLevels.indexOf(accessLevel) === -1) { + return Promise.reject(new Error(`'${accessLevel}' is not a valid access level. Must be ${offerAccessLevels.join(', ')}.`)); + } + + const mayRead = true; + const mayWrite = accessLevel === 'write' || accessLevel === 'admin'; + const mayManage = accessLevel === 'admin'; + + const permissionOffer = { + id: generateUniqueId(), + createdAt: new Date(), + updatedAt: new Date(), + expiresAt, + realmUrl, + mayRead, + mayWrite, + mayManage + }; + + return createInManagementRealm(this, 'PermissionOffer', permissionOffer) + .then(appliedOffer => appliedOffer.token); + }, + + acceptPermissionOffer(token) { + if (!token) { + return Promise.reject(new Error('Offer token must be specified')); + } + + const permissionOfferResponse = { + id: generateUniqueId(), + createdAt: new Date(), + updatedAt: new Date(), + token + }; + + return createInManagementRealm(this, 'PermissionOfferResponse', permissionOfferResponse) + .then(appliedReponse => appliedReponse.realmUrl); + }, + + invalidatePermissionOffer(permissionOfferOrToken) { + return getSpecialPurposeRealm(this, '__management', managementSchema) + .then(managementRealm => { + let permissionOffer; + + if (typeof permissionOfferOrToken === 'string') { + // We were given a token, not an object. Find the matching object. + const q = managementRealm.objects('PermissionOffer') + .filtered('token = $0', permissionOfferOrToken); + + if (q.length === 0) { + throw new Error("No permission offers with the given token were found"); + } + + permissionOffer = q[0]; + } + else { + permissionOffer = permissionOfferOrToken; + } + + managementRealm.write(() => { + permissionOffer.expiresAt = new Date(); + }); + }); + } +} diff --git a/lib/user-methods.js b/lib/user-methods.js index 34815730..03363026 100644 --- a/lib/user-methods.js +++ b/lib/user-methods.js @@ -19,6 +19,7 @@ 'use strict'; const AuthError = require('./errors').AuthError; +const permissionApis = require('./permission-api'); function node_require(module) { return require(module); @@ -139,8 +140,7 @@ function _authenticate(userConstructor, server, json, callback) { .catch(callback); } -module.exports = { - static: { +const staticMethods = { get current() { const allUsers = this.all; const keys = Object.keys(allUsers); @@ -208,8 +208,9 @@ module.exports = { }, _refreshAccessToken: refreshAccessToken - }, - instance: { + }; + +const instanceMethods = { openManagementRealm() { let url = url_parse(this.server); if (url.protocol === 'http:') { @@ -257,5 +258,12 @@ module.exports = { } }); }, - }, -}; \ No newline at end of file + }; + +// Append the permission apis +Object.assign(instanceMethods, permissionApis); + +module.exports = { + static: staticMethods, + instance: instanceMethods +}; diff --git a/tests/js/index.js b/tests/js/index.js index d3f04904..730d7419 100644 --- a/tests/js/index.js +++ b/tests/js/index.js @@ -40,6 +40,12 @@ if (!(typeof process === 'object' && process.platform === 'win32')) { if (Realm.Sync) { TESTS.UserTests = require('./user-tests'); TESTS.SessionTests = require('./session-tests'); + + // 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'); + } } function node_require(module) { return require(module); } diff --git a/tests/js/permission-tests.js b/tests/js/permission-tests.js new file mode 100644 index 00000000..02a9d10c --- /dev/null +++ b/tests/js/permission-tests.js @@ -0,0 +1,115 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2017 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'; + +var Realm = require('realm'); +var TestCase = require('./asserts'); + +function uuid() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); +} + +function createUsersWithTestRealms(count) { + const createUserWithTestRealm = username => new Promise((resolve, reject) => { + Realm.Sync.User.register('http://localhost:9080', username, 'password', (error, user) => { + if (error) { + reject(error); + } + else { + new Realm({ sync: { user, url: 'realm://localhost:9080/~/test'}}).close(); + resolve(user); + } + }) + }); + + // Generate some usernames + const usernames = new Array(count).fill(undefined).map(uuid); + + // And turn them into users and realms + const userPromises = usernames.map(createUserWithTestRealm); + return Promise.all(userPromises); +} + +function wait(t) { + return new Promise(resolve => setTimeout(resolve, t)); +} + +module.exports = { + testApplyAndGetGrantedPermissions() { + return createUsersWithTestRealms(1) + .then(([user]) => { + return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read') + .then(() => user.getGrantedPermissions('any')) + .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) + .then(([user1, user2]) => { + return user1.offerPermissions(`/${user1.identity}/test`, 'read') + .then(token => user2.acceptPermissionOffer(token)) + .then(realmUrl => { + TestCase.assertEqual(realmUrl, `/${user1.identity}/test`); + return user2.getGrantedPermissions('any') + .then(permissions => { + TestCase.assertEqual(permissions[1].path, `/${user1.identity}/test`); + TestCase.assertEqual(permissions[1].mayRead, true); + TestCase.assertEqual(permissions[1].mayWrite, false); + TestCase.assertEqual(permissions[1].mayManage, false); + }); + }); + }); + }, + + testInvalidatePermissionOffer() { + return createUsersWithTestRealms(2) + .then(([user1, user2]) => { + user1.offerPermissions(`/${user1.identity}/test`, 'read') + .then((token) => { + return user1.invalidatePermissionOffer(token) + // Since we don't yet support notification when the invalidation has gone through, + // wait for a bit and hope the server is done processing. + .then(wait(100)) + .then(user2.acceptPermissionOffer(token)) + // We want the call to fail, i.e. the catch() below should be called. + .then(() => { throw new Error("User was able to accept an invalid permission offer token"); }) + .catch(error => { + try { + TestCase.assertEqual(error.message, 'The permission offer is expired.'); + TestCase.assertEqual(error.statusCode, 701); + } + catch (e) { + throw new Error(e); + } + }); + }); + }); + }, +} + diff --git a/tests/react-test-app/index.android.js b/tests/react-test-app/index.android.js index 2b22be59..6e01d26b 100644 --- a/tests/react-test-app/index.android.js +++ b/tests/react-test-app/index.android.js @@ -51,7 +51,7 @@ async function runTests() { await runTest(suiteName, testName); } catch (e) { - itemTest.ele('error', {'message': ''}, e.message); + itemTest.ele('error', {'message': e.message, 'stacktrace': e.stack}, e.toString()); nbrFailures++; } } From 7284caf1ac885317f3f1800ad69f497d7d121ddc Mon Sep 17 00:00:00 2001 From: Kristian Dupont Date: Wed, 30 Aug 2017 13:09:40 +0200 Subject: [PATCH 13/23] Move publish testing to "test.sh all" (#1265) * Move publish testing to "test.sh all" * Minor fixes --- scripts/publish.sh | 14 +++----------- scripts/test.sh | 11 +++++++++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/scripts/publish.sh b/scripts/publish.sh index c4f96788..eac9288d 100755 --- a/scripts/publish.sh +++ b/scripts/publish.sh @@ -61,18 +61,8 @@ VERSION_BRANCH="${RELEASE_VERSION%.*}.x" git fetch origin || die 'Failed to fetch from git origin.' [ -z "$(git rev-list origin/$BRANCH..HEAD)" ] || die 'Local commits are not pushed to origin.' -# Run all tests that must pass before publishing. -for test in eslint jsdoc license-check react-example react-tests-android react-tests; do - for configuration in Debug Release; do - echo "RUNNING TEST: $test ($configuration)" - echo '----------------------------------------' - npm test "$test" "$configuration" || die "Test Failed: $test ($configuration)" - echo - done -done - # Double check before actually publishing. -confirm "Are you sure you want to publish $VERSION?" || die "Aborted publishing $VERSION" +confirm "Are you sure you want to publish $VERSION? (Did you run all tests)?" || die "Aborted publishing $VERSION" # This should fail if this tag already exists. git tag "v$VERSION" @@ -88,3 +78,5 @@ REALM_BUILD_ANDROID=1 npm publish ${PRERELEASE:+--tag $PRERELEASE} # Only push the tag to GitHub if the publish was successful. echo "Pushing v$VERSION tag to GitHub..." git push origin "v$VERSION" + +echo "Done. Now, you should update the documentation!" diff --git a/scripts/test.sh b/scripts/test.sh index c571848a..ec108956 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -396,6 +396,17 @@ case "$TARGET" in npm install --build-from-source npm run test-runners ;; +"all") + # Run all tests that must pass before publishing. + for test in eslint license-check react-example react-tests-android react-tests; do + for configuration in Debug Release; do + echo "RUNNING TEST: $test ($configuration)" + echo '----------------------------------------' + npm test "$test" "$configuration" || die "Test Failed: $test ($configuration)" + echo + done + done + ;; "object-store") pushd src/object-store cmake -DCMAKE_BUILD_TYPE="$CONFIGURATION" . From ea5cce83961f27666424435818f71d045c5a6da4 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Wed, 30 Aug 2017 20:25:52 +0530 Subject: [PATCH 14/23] Document solution to a gotcha when running tests for a new Realm API --- CONTRIBUTING.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bbc9d06b..0b18df5e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,6 +66,13 @@ void RealmClass::crashOnStart(ContextType ctx, FunctionType, ObjectType this_ Testing is important, and in `tests/js/realm-tests.js` you can add the tests you need. +Note: If your new API and/or test cases are not getting picked up when running the Android or iOS tests, remove the corresponding installed package from react-test-app and try again. + +``` +rm -rf tests/react-test-app/node_modules/realm +rm -rf tests/react-test-app/node_modules/realm-tests +``` + #### Wrap the function In order to call the C++ implementation, the JavaScript engine has to know about the function. You must simply add it to the map of methods/functions. Find `MethodMap const methods` declaration in `src/js_realm.hpp` and add your function to it: From 8d7eac1b65fededabc8b083db593683c370e9981 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Thu, 31 Aug 2017 10:18:10 +0200 Subject: [PATCH 15/23] Fix linter error (#1271) --- lib/extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/extensions.js b/lib/extensions.js index f2f034c5..48afae4c 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -107,7 +107,7 @@ module.exports = function(realmConstructor) { if (realmConstructor.Sync._setFeatureToken) { realmConstructor.Sync.setFeatureToken = function(featureToken) { - if (typeof featureToken !== 'string' || !featureToken instanceof String) { + if (typeof featureToken !== 'string' || !(featureToken instanceof String)) { throw new Error("featureToken should be a string"); } From de94dc0e16366fdda46da23bf429df3d5b83375b Mon Sep 17 00:00:00 2001 From: kristiandupont Date: Thu, 31 Aug 2017 12:52:36 +0200 Subject: [PATCH 16/23] [1.11.0] Bump version --- CHANGELOG.md | 2 +- dependencies.list | 2 +- package-lock.json | 110 +++++++++++++++++++------- package.json | 2 +- src/RealmJS.xcodeproj/project.pbxproj | 4 +- 5 files changed, 86 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa26cea6..ea0b371d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -X.Y.Z Release notes +1.11.0 Release notes (2017-8-31) ============================================================= ### Breaking changes * None diff --git a/dependencies.list b/dependencies.list index 44d626d1..3b0a939d 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=1.10.3 +VERSION=1.11.0 REALM_CORE_VERSION=2.8.6 REALM_SYNC_VERSION=1.10.5 REALM_OBJECT_SERVER_VERSION=1.8.2 diff --git a/package-lock.json b/package-lock.json index aa5f2159..54eee255 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "realm", - "version": "1.10.2", + "version": "1.11.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -12,7 +12,7 @@ "acorn": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz", - "integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==", + "integrity": "sha1-U/4WERH5EquZnuiHqQoLxSgi/XU=", "dev": true }, "acorn-jsx": { @@ -67,7 +67,7 @@ "aproba": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.1.2.tgz", - "integrity": "sha512-ZpYajIfO0j2cOFTO955KUMIKNmj6zhX8kVztMAxFsDaMwz+9Z9SV0uou2pC9HJqcfpffOsjnbrDMvkNy+9RXPw==" + "integrity": "sha1-RcZikJTeTpb2k+9+q3SuB5wkD8E=" }, "are-we-there-yet": { "version": "1.1.4", @@ -322,7 +322,7 @@ "circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "integrity": "sha1-gVyZ6oT2gJUp0vRXkb34JxE1LWY=", "dev": true }, "cli-cursor": { @@ -521,7 +521,7 @@ "es-abstract": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.8.0.tgz", - "integrity": "sha512-Cf9/h5MrXtExM20gSS55YFrGKCyPrRBjIVBtVyy8vmlsDfe0NPKMWj65tPLgzyfPuapWxh5whpXCtW4+AW5mRg==", + "integrity": "sha1-OwA4XoVymTK+/6kWO76hI06TKRQ=", "dev": true, "requires": { "es-to-primitive": "1.1.1", @@ -723,7 +723,7 @@ "esprima": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", + "integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ=", "dev": true }, "esquery": { @@ -800,6 +800,27 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fbjs": { + "version": "0.8.14", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.14.tgz", + "integrity": "sha1-0dviviVMNakeCfMfnNUKQLKg7Rw=", + "requires": { + "core-js": "1.2.7", + "isomorphic-fetch": "2.2.1", + "loose-envify": "1.3.1", + "object-assign": "4.1.1", + "promise": "7.3.1", + "setimmediate": "1.0.5", + "ua-parser-js": "0.7.14" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + } + } + }, "fd-slicer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", @@ -941,7 +962,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", "requires": { "fs.realpath": "1.0.0", "inflight": "1.0.6", @@ -954,7 +975,7 @@ "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "integrity": "sha1-qjiWs+abSH8X4x7SFD1pqOMMLYo=", "dev": true }, "globby": { @@ -1032,7 +1053,7 @@ "hosted-git-info": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", - "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "integrity": "sha1-bWDjSzq7yDEwYsO3mO+NkBoHrzw=", "dev": true }, "http-basic": { @@ -1070,7 +1091,7 @@ "iconv-lite": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.18.tgz", - "integrity": "sha512-sr1ZQph3UwHTR0XftSbK85OvBbxe/abLGzEnPENCQwmHf7sck8Oyu4ob3LgBxWWxRoM+QszeUyl7jbqapu2TqA==" + "integrity": "sha1-I9hlaxaq5nQqwpcy6o8DNqR4nPI=" }, "ignore": { "version": "3.3.3", @@ -1249,6 +1270,15 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "isomorphic-fetch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", + "requires": { + "node-fetch": "1.7.2", + "whatwg-fetch": "2.0.3" + } + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", @@ -1263,13 +1293,12 @@ "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" }, "js-yaml": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.1.tgz", - "integrity": "sha512-CbcG379L1e+mWBnLvHWWeLs8GyV/EMw862uLI3c+GxVyDHWZcjZinwuBd3iW2pgxgIlksW/1vNJa4to+RvDOww==", + "integrity": "sha1-CHdc69/dNZIJ8NKs04PI+GppBKA=", "dev": true, "requires": { "argparse": "1.0.9", @@ -1294,7 +1323,7 @@ "jsdoc": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.4.tgz", - "integrity": "sha512-VmTw0J+2L16IxAe0JSDSAcH0F+DbZxaj8wN1AjHtKMQU/hO0ciIl5ZE93XqrrFIbknobuqHKJCXZj6+Hk57MjA==", + "integrity": "sha1-zu73xLrEM1yxD/QeOg9Yk5pTRCg=", "dev": true, "requires": { "babylon": "7.0.0-beta.19", @@ -1512,7 +1541,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "dev": true, "requires": { "js-tokens": "3.0.2" } @@ -1539,7 +1567,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", "requires": { "brace-expansion": "1.1.8" } @@ -1560,7 +1588,7 @@ "mockery": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mockery/-/mockery-2.1.0.tgz", - "integrity": "sha512-9VkOmxKlWXoDO/h1jDZaS4lH33aWfRiJiNT/tKj+8OGzrcFDLo8d0syGdbsc3Bc4GvRXPb+NMMvojotmuGJTvA==", + "integrity": "sha1-WwrvH/Vk8PgTlEXhZVNseQlxNHA=", "dev": true }, "ms": { @@ -1588,7 +1616,7 @@ "node-fetch": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.2.tgz", - "integrity": "sha512-xZZUq2yDhKMIn/UgG5q//IZSNLJIwW2QxS14CNH5spuiXkITM2pUitjdq58yLSaU7m4M0wBNaM2Gh/ggY4YJig==", + "integrity": "sha1-xU6arFfkModSM1JfPIkcQVn/79c=", "requires": { "encoding": "0.1.12", "is-stream": "1.1.0" @@ -1632,7 +1660,7 @@ "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "integrity": "sha1-EvlaMH1YNSB1oEkHuErIvpisAS8=", "dev": true, "requires": { "hosted-git-info": "2.5.0", @@ -1644,7 +1672,7 @@ "npmlog": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "integrity": "sha1-CKfyqL9zRgR3mp76StXMcXq7lUs=", "requires": { "are-we-there-yet": "1.1.4", "console-control-strings": "1.1.0", @@ -1805,11 +1833,20 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", "requires": { "asap": "2.0.6" } }, + "prop-types": { + "version": "15.5.10", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.5.10.tgz", + "integrity": "sha1-J5ffwxJhguOpXj37suiT3ddFYVQ=", + "requires": { + "fbjs": "0.8.14", + "loose-envify": "1.3.1" + } + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -1873,7 +1910,7 @@ "readable-stream": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", "requires": { "core-util-is": "1.0.2", "inherits": "2.0.3", @@ -1986,7 +2023,7 @@ "resolve": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", - "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", + "integrity": "sha1-p1vgHFPaJdk0qY69DkxKcxL5KoY=", "dev": true, "requires": { "path-parse": "1.0.5" @@ -2034,18 +2071,23 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=" }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" + "integrity": "sha1-4FnAnYVx8FQII3M0M1BdOi8AsY4=" }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, "shelljs": { "version": "0.7.8", "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", @@ -2153,7 +2195,7 @@ "string_decoder": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "requires": { "safe-buffer": "5.1.1" } @@ -2237,7 +2279,7 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=", "dev": true, "requires": { "is-fullwidth-code-point": "2.0.0", @@ -2372,6 +2414,11 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "ua-parser-js": { + "version": "0.7.14", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.14.tgz", + "integrity": "sha1-EQ1T+kw/MmwSEpK76skE0uAzh8o=" + }, "uid-number": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", @@ -2432,7 +2479,7 @@ "uuid": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", - "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" + "integrity": "sha1-PdPT55Crwk17DToDT/q6vijrvAQ=" }, "validate-npm-package-license": { "version": "3.0.1", @@ -2461,10 +2508,15 @@ } } }, + "whatwg-fetch": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", + "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" + }, "wide-align": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz", - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=", "requires": { "string-width": "1.0.2" } diff --git a/package.json b/package.json index cbf7353a..cb4a04bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "1.10.3", + "version": "1.11.0", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index db254caf..62d4b153 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -956,7 +956,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.10.3; + CURRENT_PROJECT_VERSION = 1.11.0; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1020,7 +1020,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.10.3; + CURRENT_PROJECT_VERSION = 1.11.0; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; From 0c61716c0807cc46c54598ee55e6986d56aebd68 Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 1 Sep 2017 11:53:52 +0200 Subject: [PATCH 17/23] Fix accessToken (#1275) --- lib/extensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/extensions.js b/lib/extensions.js index 48afae4c..7b086554 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -107,7 +107,7 @@ module.exports = function(realmConstructor) { if (realmConstructor.Sync._setFeatureToken) { realmConstructor.Sync.setFeatureToken = function(featureToken) { - if (typeof featureToken !== 'string' || !(featureToken instanceof String)) { + if (typeof featureToken !== 'string' && !(featureToken instanceof String)) { throw new Error("featureToken should be a string"); } From 806146b07cb5d708eb2aab840da0f688f7abb67c Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 1 Sep 2017 13:07:37 +0200 Subject: [PATCH 18/23] [1.11.1] Bump version --- CHANGELOG.md | 12 ++++++++++++ dependencies.list | 2 +- package.json | 2 +- src/RealmJS.xcodeproj/project.pbxproj | 4 ++-- 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea0b371d..2820b986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +1.11.1 Release notes (2017-9-1) +============================================================= +### Breaking changes +* None + +### Enhancements +* None + +### Bug fixes +* Fix accessToken. + + 1.11.0 Release notes (2017-8-31) ============================================================= ### Breaking changes diff --git a/dependencies.list b/dependencies.list index 3b0a939d..26acf24b 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=1.11.0 +VERSION=1.11.1 REALM_CORE_VERSION=2.8.6 REALM_SYNC_VERSION=1.10.5 REALM_OBJECT_SERVER_VERSION=1.8.2 diff --git a/package.json b/package.json index cb4a04bb..cf448266 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "1.11.0", + "version": "1.11.1", "license": "Apache-2.0", "homepage": "https://realm.io", "keywords": [ diff --git a/src/RealmJS.xcodeproj/project.pbxproj b/src/RealmJS.xcodeproj/project.pbxproj index 62d4b153..1d7f69aa 100644 --- a/src/RealmJS.xcodeproj/project.pbxproj +++ b/src/RealmJS.xcodeproj/project.pbxproj @@ -956,7 +956,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.11.0; + CURRENT_PROJECT_VERSION = 1.11.1; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -1020,7 +1020,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1.11.0; + CURRENT_PROJECT_VERSION = 1.11.1; CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; From 360a5fbd078cd0392087c0387dc51c6694dffb8e Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 31 Aug 2017 15:29:57 -0700 Subject: [PATCH 19/23] Don't throw an exception every 10ms in the RPC worker Cuts the time taken to run the react native tests on my machine from 198 seconds to 41 seconds. Probably has a similar impact on the react native debugger performance. --- src/concurrent_deque.hpp | 48 ++++++++++++++-------------------------- src/rpc.cpp | 19 ++++++++-------- 2 files changed, 26 insertions(+), 41 deletions(-) diff --git a/src/concurrent_deque.hpp b/src/concurrent_deque.hpp index 4c05b372..260ff107 100644 --- a/src/concurrent_deque.hpp +++ b/src/concurrent_deque.hpp @@ -20,37 +20,26 @@ #include #include -#include #include +#include + namespace realm { -class ConcurrentDequeTimeout : public std::exception { - public: - ConcurrentDequeTimeout() : std::exception() {} -}; - template class ConcurrentDeque { - public: - T pop_front(size_t timeout = 0) { +public: + T pop_back() { std::unique_lock lock(m_mutex); - while (m_deque.empty()) { - wait(lock, timeout); - } - T item = std::move(m_deque.front()); - m_deque.pop_front(); - return item; + m_condition.wait(lock, [this] { return !m_deque.empty(); }); + return do_pop_back(); } - T pop_back(size_t timeout = 0) { + util::Optional try_pop_back(size_t timeout) { std::unique_lock lock(m_mutex); - while (m_deque.empty()) { - wait(lock, timeout); - } - T item = std::move(m_deque.back()); - m_deque.pop_back(); - return item; + m_condition.wait_for(lock, std::chrono::milliseconds(timeout), + [this] { return !m_deque.empty(); }); + return m_deque.empty() ? util::none : util::make_optional(do_pop_back()); } void push_front(T&& item) { @@ -72,19 +61,16 @@ class ConcurrentDeque { return m_deque.empty(); } - void wait(std::unique_lock &lock, size_t timeout = 0) { - if (!timeout) { - m_condition.wait(lock); - } - else if (m_condition.wait_for(lock, std::chrono::milliseconds(timeout)) == std::cv_status::timeout) { - throw ConcurrentDequeTimeout(); - } - } - - private: +private: std::condition_variable m_condition; std::mutex m_mutex; std::deque m_deque; + + T do_pop_back() { + T item = std::move(m_deque.back()); + m_deque.pop_back(); + return item; + } }; } // realm diff --git a/src/rpc.cpp b/src/rpc.cpp index 32e693ed..896fad8a 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -77,17 +77,16 @@ json RPCWorker::pop_task_result() { } void RPCWorker::try_run_task() { - try { - // Use a 10 millisecond timeout to keep this thread unblocked. - auto task = m_tasks.pop_back(10); - task(); + // Use a 10 millisecond timeout to keep this thread unblocked. + auto task = m_tasks.try_pop_back(10); + if (!task) { + return; + } - // Since this can be called recursively, it must be pushed to the front of the queue *after* running the task. - m_futures.push_front(task.get_future()); - } - catch (ConcurrentDequeTimeout &) { - // We tried. - } + (*task)(); + + // Since this can be called recursively, it must be pushed to the front of the queue *after* running the task. + m_futures.push_front(task->get_future()); } void RPCWorker::stop() { From ca9263be4fa409664c229ee2a725c0830bfc1cfc Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Fri, 1 Sep 2017 10:32:47 -0700 Subject: [PATCH 20/23] Add changelog entry --- CHANGELOG.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2820b986..0b31ca49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +X.Y.Z Release notes +============================================================= +### Breaking changes +* None + +### Enhancements +* Improve performance of the RPC worker for chrome debugging. + +### Bug fixes +* None + 1.11.1 Release notes (2017-9-1) ============================================================= ### Breaking changes @@ -9,7 +20,6 @@ ### Bug fixes * Fix accessToken. - 1.11.0 Release notes (2017-8-31) ============================================================= ### Breaking changes From 5353964804f594fc0b01e9b99a665d11803867eb Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Tue, 5 Sep 2017 15:01:55 +0100 Subject: [PATCH 21/23] Update Jenkinsfile (#1282) * Update Jenkinsfile Specify build nodes using granular tags * Update Jenkinsfile go back to building react tests on the vegas nodes --- Jenkinsfile | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 85629f2d..9848c78f 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -68,8 +68,8 @@ stage('build') { macos_node_release: doMacBuild('node Release'), //macos_realmjs_debug: doMacBuild('realmjs Debug'), //macos_realmjs_release: doMacBuild('realmjs Release'), - macos_react_tests_debug: doReactBuild('react-tests Debug'), - macos_react_tests_release: doReactBuild('react-tests Release'), + macos_react_tests_debug: doMacBuild('react-tests Debug'), + macos_react_tests_release: doMacBuild('react-tests Release'), macos_react_example_debug: doMacBuild('react-example Debug'), macos_react_example_release: doMacBuild('react-example Release'), //android_react_tests: doAndroidBuild('react-tests-android', { @@ -188,26 +188,12 @@ def doMacBuild(target, postStep = null) { } } -def doReactBuild(target, postStep = null) { - return { - node('xamarin-mac') { - try { - lock("${env.NODE_NAME} iOS Simulator") { - doInside("./scripts/test.sh", target, postStep) - } - } finally { - deleteDir() - } - } - } -} - def doWindowsBuild() { return { - node('windows') { + node('windows && nodejs') { unstash 'source' try { - bat 'npm install --build-from-source' + bat 'npm install --build-from-source=realm' dir('tests') { bat 'npm install' bat 'npm run test' From 119cd79e5db47ab4776b9799ddf4fd5769379ab5 Mon Sep 17 00:00:00 2001 From: Ashwin Phatak Date: Wed, 30 Aug 2017 10:25:30 +0530 Subject: [PATCH 22/23] Add Realm.deleteFile API (#363) --- CHANGELOG.md | 2 ++ docs/realm.js | 7 ++++++ lib/browser/index.js | 5 ++++ lib/index.d.ts | 6 +++++ src/android/platform.cpp | 15 ++++++++++++ src/ios/platform.mm | 17 +++++++++++++- src/js_realm.hpp | 33 ++++++++++++++++++++++++++ src/node/platform.cpp | 51 ++++++++++++++++++++++++++++++++++++++++ src/platform.hpp | 6 +++++ tests/js/realm-tests.js | 36 ++++++++++++++++++++++++++++ 10 files changed, 177 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b31ca49..787f1375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ X.Y.Z Release notes ### Enhancements * Improve performance of the RPC worker for chrome debugging. +* Added `Realm.deleteFile` for deleting a Realm (#363). ### Bug fixes * None @@ -35,6 +36,7 @@ X.Y.Z Release notes ### Bug fixes * None + 1.10.3 Release notes (2017-8-16) ============================================================= ### Breaking changes diff --git a/docs/realm.js b/docs/realm.js index a4d667aa..05d5ead5 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -237,6 +237,13 @@ class Realm { */ Realm.schemaVersion = function(path, encryptionKey) {}; +/** + * Delete the Realm file for the given configuration. + * @param {Realm~Configuration} config + * @throws {Error} If anything in the provided `config` is invalid. + */ +Realm.deleteFile = function(config) {}; + /** * The default path where to create and access the Realm file. * @type {string} diff --git a/lib/browser/index.js b/lib/browser/index.js index ccecf45e..8caff788 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -169,6 +169,11 @@ Object.defineProperties(Realm, { return rpc.callMethod(undefined, Realm[keys.id], 'schemaVersion', Array.from(arguments)); } }, + deleteFile: { + value: function(config) { + return rpc.callMethod(undefined, Realm[keys.id], 'deleteFile', Array.from(arguments)); + } + }, copyBundledRealmFiles: { value: function() { return rpc.callMethod(undefined, Realm[keys.id], 'copyBundledRealmFiles', []); diff --git a/lib/index.d.ts b/lib/index.d.ts index 4ecfd612..614dd52d 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -423,6 +423,12 @@ declare class Realm { */ static openAsync(config: Realm.Configuration, callback: (error: any, realm: Realm) => void): void + /** + * Delete the Realm file for the given configuration. + * @param {Configuration} config + */ + static deleteFile(config: Realm.Configuration): void + /** * @param {Realm.Configuration} config? */ diff --git a/src/android/platform.cpp b/src/android/platform.cpp index e008169f..e26ba655 100644 --- a/src/android/platform.cpp +++ b/src/android/platform.cpp @@ -93,4 +93,19 @@ namespace realm { s_default_realm_directory + "/*.realm.lock"; system(cmd.c_str()); } + + void remove_directory(const std::string &path) + { + std::string cmd_clear_dir = "rm " + path + "/*"; + system(cmd_clear_dir.c_str()); + + std::string cmd_rmdir = "rmdir " + path; + system(cmd_rmdir.c_str()); + } + + void remove_file(const std::string &path) + { + std::string cmd = "rm " + path; + system(cmd.c_str()); + } } diff --git a/src/ios/platform.mm b/src/ios/platform.mm index 26d93075..c412f8d6 100644 --- a/src/ios/platform.mm +++ b/src/ios/platform.mm @@ -101,5 +101,20 @@ void remove_realm_files_from_directory(const std::string &directory) } } } - + +void remove_file(const std::string &path) +{ + NSFileManager *manager = [NSFileManager defaultManager]; + NSString *filePath = @(path.c_str()); + + if (![manager removeItemAtPath:filePath error:nil]) { + throw std::runtime_error((std::string)"Failed to delete file at path " + filePath.UTF8String); + } +} + +void remove_directory(const std::string &path) +{ + remove_file(path); // works for directories too +} + } diff --git a/src/js_realm.hpp b/src/js_realm.hpp index c9702575..848a2ccc 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -200,6 +200,7 @@ public: 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 void delete_file(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); // static properties static void get_default_path(ContextType, ObjectType, ReturnValue &); @@ -211,6 +212,7 @@ public: {"schemaVersion", wrap}, {"clearTestState", wrap}, {"copyBundledRealmFiles", wrap}, + {"deleteFile", wrap}, {"_waitForDownload", wrap}, }; @@ -526,6 +528,37 @@ void RealmClass::copy_bundled_realm_files(ContextType ctx, FunctionType, Obje realm::copy_bundled_realm_files(); } +template +void RealmClass::delete_file(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { + validate_argument_count(argc, 1); + + ValueType value = arguments[0]; + if (!Value::is_object(ctx, value)) { + throw std::runtime_error("Invalid argument, expected a Realm configuration object"); + } + + ObjectType object = Value::validated_to_object(ctx, value); + realm::Realm::Config config; + + static const String path_string = "path"; + ValueType path_value = Object::get_property(ctx, object, path_string); + if (!Value::is_undefined(ctx, path_value)) { + config.path = Value::validated_to_string(ctx, path_value, "path"); + } + else if (config.path.empty()) { + config.path = js::default_path(); + } + + config.path = normalize_realm_path(config.path); + + std::string realm_file_path = config.path; + realm::remove_file(realm_file_path); + realm::remove_file(realm_file_path + ".lock"); + realm::remove_file(realm_file_path + ".note"); + realm::remove_directory(realm_file_path + ".management"); + +} + template void RealmClass::get_default_path(ContextType ctx, ObjectType object, ReturnValue &return_value) { return_value.set(realm::js::default_path()); diff --git a/src/node/platform.cpp b/src/node/platform.cpp index 4d23e587..9a16fe85 100644 --- a/src/node/platform.cpp +++ b/src/node/platform.cpp @@ -134,4 +134,55 @@ void remove_realm_files_from_directory(const std::string &dir_path) } } +void remove_directory(const std::string &path) +{ + FileSystemRequest exists_req; + if (uv_fs_stat(uv_default_loop(), &exists_req, path.c_str(), nullptr) != 0) { + if (exists_req.result == UV_ENOENT) { + // path doesn't exist, ignore + return; + } else { + throw UVException(static_cast(exists_req.result)); + } + } + + uv_dirent_t dir_entry; + FileSystemRequest dir_scan_req; + if (uv_fs_scandir(uv_default_loop(), &dir_scan_req, path.c_str(), 0, nullptr) < 0) { + throw UVException(static_cast(dir_scan_req.result)); + } + + while (uv_fs_scandir_next(&dir_scan_req, &dir_entry) != UV_EOF) { + std::string dir_entry_path = path + '/' + dir_entry.name; + FileSystemRequest delete_req; + if (uv_fs_unlink(uv_default_loop(), &delete_req, dir_entry_path.c_str(), nullptr) != 0) { + throw UVException(static_cast(delete_req.result)); + } + } + + FileSystemRequest rmdir_req; + if (uv_fs_rmdir(uv_default_loop(), &rmdir_req, path.c_str(), nullptr)) { + throw UVException(static_cast(rmdir_req.result)); + } +} + + +void remove_file(const std::string &path) +{ + FileSystemRequest exists_req; + if (uv_fs_stat(uv_default_loop(), &exists_req, path.c_str(), nullptr) != 0) { + if (exists_req.result == UV_ENOENT) { + // path doesn't exist, ignore + return; + } else { + throw UVException(static_cast(exists_req.result)); + } + } + + FileSystemRequest delete_req; + if (uv_fs_unlink(uv_default_loop(), &delete_req, path.c_str(), nullptr) != 0) { + throw UVException(static_cast(delete_req.result)); + } +} + } // realm diff --git a/src/platform.hpp b/src/platform.hpp index 56afc299..b56037bd 100644 --- a/src/platform.hpp +++ b/src/platform.hpp @@ -41,4 +41,10 @@ void copy_bundled_realm_files(); // remove all realm files in the given directory void remove_realm_files_from_directory(const std::string &directory); +// remove file at the given path +void remove_file(const std::string &path); + +// remove directory at the given path +void remove_directory(const std::string &path); + } diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index 4a66bc5c..e98db623 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -1030,5 +1030,41 @@ module.exports = { const realm1 = new Realm({schema: [schemas.StringOnly]}); const realm2 = new Realm({schema: [schemas.StringOnly]}); TestCase.assertThrows(realm1.compact()); + }, + + testRealmDeleteFileDefaultConfigPath: function() { + const config = {schema: [schemas.TestObject]}; + const realm = new Realm(config); + + realm.write(function() { + realm.create('TestObject', {doubleCol: 1}); + }); + + TestCase.assertEqual(realm.objects('TestObject').length, 1); + realm.close(); + + Realm.deleteFile(config); + + const realm2 = new Realm(config); + TestCase.assertEqual(realm2.objects('TestObject').length, 0); + realm.close(); + }, + + testRealmDeleteFileCustomConfigPath: function() { + const config = {schema: [schemas.TestObject], path: 'test-realm-delete-file.realm'}; + const realm = new Realm(config); + + realm.write(function() { + realm.create('TestObject', {doubleCol: 1}); + }); + + TestCase.assertEqual(realm.objects('TestObject').length, 1); + realm.close(); + + Realm.deleteFile(config); + + const realm2 = new Realm(config); + TestCase.assertEqual(realm2.objects('TestObject').length, 0); + realm.close(); } }; From 38bceddfb19fd9f8ac5cc62ddb8bfa626270aa5c Mon Sep 17 00:00:00 2001 From: Kenneth Geisshirt Date: Fri, 8 Sep 2017 10:19:13 +0200 Subject: [PATCH 23/23] Add missing TypeScript declaration (#1285) * Adding missing TypeScript declaration --- CHANGELOG.md | 2 +- lib/index.d.ts | 19 +++++++++++++++---- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 787f1375..4dfb3639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ X.Y.Z Release notes * Added `Realm.deleteFile` for deleting a Realm (#363). ### Bug fixes -* None +* Adding missing TypeScript declation (#1283). 1.11.1 Release notes (2017-9-1) ============================================================= diff --git a/lib/index.d.ts b/lib/index.d.ts index 614dd52d..f1f2039a 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -279,12 +279,23 @@ declare namespace Realm.Sync { invalidatePermissionOffer(permissionOfferOrToken: PermissionOffer | string): Promise; } - type PermissionCondition = - { [object_type: string]: userId } | - { [object_type: string]: metadataKey, [object_type: string]: metadataValue }; - + type PermissionCondition = { + userId: string | + { metadataKey: string, metadataValue: string } + }; + type AccessLevel = 'none' | 'read' | 'write' | 'admin'; + class Permission { + readonly id: string; + readonly updatedAt: Date; + readonly userId: string; + readonly path: string; + readonly mayRead?: boolean; + readonly mayWrite?: boolean; + readonly mayManage?: boolean; + } + class PermissionChange { id: string; createdAt: Date;