From 78515e3b487467536de7fc0d4b42df8efda7ed40 Mon Sep 17 00:00:00 2001 From: Maximilian Alexander Date: Thu, 24 Aug 2017 11:01:12 -0700 Subject: [PATCH 01/14] adding promisified apis --- CHANGELOG.md | 2 +- docs/sync.js | 9 ++-- lib/extensions.js | 6 ++- lib/index.d.ts | 5 +- lib/user-methods.js | 53 +++++++++++++++----- tests/js/session-tests.js | 40 +++------------ tests/js/user-tests.js | 100 ++++++++++++++++---------------------- 7 files changed, 105 insertions(+), 110 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be46681f..0f2538f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ X.Y.Z Release notes * Added various methods for permission management (#1204). ### Bug fixes -* None +* Removed `loginWithProvider` from TypeScript definition files. This API never existed and was incorrectly added. 1.10.3 Release notes (2017-8-16) diff --git a/docs/sync.js b/docs/sync.js index f2f25bff..609f1663 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -135,9 +135,10 @@ class User { * @param {string} server - authentication server * @param {string} username * @param {string} password - * @param {function(error, user)} callback - called with the following arguments: + * @param {function(error, user)} [callback] - called with the following arguments: * - `error` - an Error object is provided on failure * - `user` - a valid User object on success + * @returns {Promise} Returns a promise with a user only if the callback was not specified */ static login(server, username, password, callback) {} @@ -148,9 +149,10 @@ class User { * @param {string} options.provider - The provider type * @param {string} options.providerToken - The access token for the given provider * @param {object} [options.userInfo] - A map containing additional data required by the provider - * @param {function(error, User)} callback - called with the following arguments: + * @param {function(error, User)} [callback] - an optional called with the following arguments: * - `error` - an Error object is provided on failure * - `user` - a valid User object on success + * @return {Promise} Returns a promise with a user only if the callback was not specified */ static registerWithProvider(server, options, callback) {} @@ -159,9 +161,10 @@ class User { * @param {string} server - authentication server * @param {string} username * @param {string} password - * @param {function(error, user)} callback - called with the following arguments: + * @param {function(error, user)} [callback] - called with the following arguments: * - `error` - an Error object is provided on failure * - `user` - a valid User object on success + * @return {Promise} Returns a promise with a user only if the callback was not specified */ static register(server, username, password, callback) {} diff --git a/lib/extensions.js b/lib/extensions.js index 3ec8f6cf..4e1d3de0 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -77,7 +77,9 @@ module.exports = function(realmConstructor) { }, openAsync(config, progressCallback, callback) { - + const message = "Realm.openAsync is now deprecated in favor of Realm.open. This function will be removed in future versions."; + (console.warn || console.log).call(console, message); + if (!callback) { callback = progressCallback; progressCallback = null; @@ -99,7 +101,7 @@ module.exports = function(realmConstructor) { //FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented setTimeout(() => { callback(null, syncedRealm); }, 1); } catch (e) { - callback(e); + setTimeout(() => { callback(e); }, 1); } } }); diff --git a/lib/index.d.ts b/lib/index.d.ts index 9917de85..242174eb 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -264,9 +264,11 @@ declare namespace Realm.Sync { readonly server: string; readonly token: string; static adminUser(adminToken: string, server?: string): User; + static login(server: string, username: string, password: string): Promise; static login(server: string, username: string, password: string, callback: (error: any, user: User) => void): void; - static loginWithProvider(server: string, provider: string, providerToken: string, callback: (error: any, user: User) => void): void; + static register(server: string, username: string, password: string): Promise; static register(server: string, username: string, password: string, callback: (error: any, user: User) => void): void; + static registerWithProvider(server: string, options: { provider: string, providerToken: string, userInfo: any }): Promise; static registerWithProvider(server: string, options: { provider: string, providerToken: string, userInfo: any }, callback: (error: Error | null, user: User | null) => void): void; logout(): void; openManagementRealm(): Realm; @@ -446,6 +448,7 @@ declare class Realm { */ static open(config: Realm.Configuration): ProgressPromise; /** + * @deprecated in favor of `Realm.open` * Open a realm asynchronously with a callback. If the realm is synced, it will be fully synchronized before it is available. * @param {Configuration} config * @param {ProgressNotificationCallback} progressCallback? a progress notification callback for 'download' direction and 'forCurrentlyOutstandingWork' mode diff --git a/lib/user-methods.js b/lib/user-methods.js index 7994d71d..0b2d9d4e 100644 --- a/lib/user-methods.js +++ b/lib/user-methods.js @@ -114,6 +114,15 @@ function refreshAccessToken(user, localRealmPath, realmUrl) { }); } +/** + * The base authentication method. It fires a JSON POST to the server parameter plus the auth url + * For example, if the server parameter is `http://myapp.com`, this url will post to `http://myapp.com/auth` + * @param {object} userConstructor + * @param {string} server the http or https server url + * @param {object} json the json to post to the auth endpoint + * @param {Function} callback an optional callback with an error and user parameter + * @returns {Promise} only returns a promise if the callback parameter was omitted + */ function _authenticate(userConstructor, server, json, callback) { json.app_id = ''; const url = auth_url(server); @@ -123,7 +132,7 @@ function _authenticate(userConstructor, server, json, callback) { headers: postHeaders, open_timeout: 5000 }; - performFetch(url, options) + const promise = performFetch(url, options) .then((response) => { if (response.status !== 200) { return response.json().then((body) => callback(new AuthError(body))); @@ -136,8 +145,18 @@ function _authenticate(userConstructor, server, json, callback) { callback(undefined, userConstructor.createUser(server, identity, token, false, isAdmin)); }) } + }); + + if (callback) { + promise.then(user => { + callback(null, user); }) - .catch(callback); + .catch(err => { + callback(err) + }); + } else { + return promise; + } } const staticMethods = { @@ -164,20 +183,30 @@ const staticMethods = { register(server, username, password, callback) { checkTypes(arguments, ['string', 'string', 'string', 'function']); - _authenticate(this, server, { + const json = { provider: 'password', user_info: { password: password, register: true }, data: username - }, callback); + }; + if (callback) { + _authenticate(this, server, json, callback); + } else { + return _authenticate(this, server) + } }, login(server, username, password, callback) { checkTypes(arguments, ['string', 'string', 'string', 'function']); - _authenticate(this, server, { + const json = { provider: 'password', user_info: { password: password }, data: username - }, callback); + }; + if (callback) { + _authenticate(this, server, json, callback); + } else { + return _authenticate(this, server) + } }, registerWithProvider(server, options, callback) { @@ -195,16 +224,20 @@ const staticMethods = { checkTypes(arguments, ['string', 'object', 'function']); } - let reqOptions = { + let json = { provider: options.provider, data: options.providerToken, }; if (options.userInfo) { - reqOptions.user_info = options.userInfo; + json.user_info = options.userInfo; } - _authenticate(this, server, reqOptions, callback); + if (callback) { + _authenticate(this, server, json, callback); + } else { + return _authenticate(this, server) + } }, _refreshAccessToken: refreshAccessToken @@ -233,13 +266,11 @@ const instanceMethods = { }, retrieveAccount(provider, provider_id) { checkTypes(arguments, ['string', 'string']); - const url = url_parse(this.server); url.set('pathname', `/api/providers/${provider}/accounts/${provider_id}`); const headers = { Authorization: this.token }; - const options = { method: 'GET', headers, diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 153bd6e9..6f61bcfd 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -50,32 +50,6 @@ function uuid() { }); } -function promisifiedRegister(server, username, password) { - return new Promise((resolve, reject) => { - Realm.Sync.User.register(server, username, password, (error, user) => { - if (error) { - console.log(`promisifiedRegister ${error}`); - reject(error); - } else { - resolve(user); - } - }); - }); -} - -function promisifiedLogin(server, username, password) { - return new Promise((resolve, reject) => { - Realm.Sync.User.login(server, username, password, (error, user) => { - if (error) { - console.log(`promisifiedLogin ${error}`); - reject(error); - } else { - resolve(user); - } - }); - }); -} - function runOutOfProcess(nodeJsFilePath) { var nodeArgs = Array.prototype.slice.call(arguments); let tmpDir = tmp.dirSync(); @@ -111,7 +85,7 @@ module.exports = { }, testProperties() { - return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { + return Realm.Sync.User.login('http://localhost:9080', uuid(), 'password').then(user => { return new Promise((resolve, reject) => { const accessTokenRefreshed = this; @@ -162,7 +136,7 @@ module.exports = { return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { const accessTokenRefreshed = this; let successCounter = 0; @@ -198,7 +172,7 @@ module.exports = { return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { return new Promise((resolve, reject) => { const accessTokenRefreshed = this; let successCounter = 0; @@ -251,7 +225,7 @@ module.exports = { return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { const accessTokenRefreshed = this; let successCounter = 0; let progressNotificationCalled = false; @@ -287,7 +261,7 @@ module.exports = { return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { return new Promise((resolve, reject) => { let progressNotificationCalled = false; let config = { @@ -334,7 +308,7 @@ module.exports = { return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { return new Promise((resolve, reject) => { const accessTokenRefreshed = this; let successCounter = 0; @@ -439,7 +413,7 @@ module.exports = { }, testErrorHandling() { - return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { + return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then(user => { return new Promise((resolve, _reject) => { const config = { sync: { user, url: 'realm://localhost:9080/~/myrealm' } }; config.sync.error = (sender, error) => { diff --git a/tests/js/user-tests.js b/tests/js/user-tests.js index 4d2a24ab..466f80ca 100644 --- a/tests/js/user-tests.js +++ b/tests/js/user-tests.js @@ -297,73 +297,55 @@ module.exports = { }, testRetrieveAccount() { - return new Promise((resolve, reject) => { - if (!isNodeProcess) { - resolve(); - } - - if (!global.testAdminUserInfo) { - reject("Test requires an admin user"); - } - - Realm.Sync.User.login('http://localhost:9080', global.testAdminUserInfo.username, global.testAdminUserInfo.password, (error, user) => { - if (error) { - reject(error); - } - + if (!isNodeProcess) { + return Promise.resolve() + } + if (!global.testAdminUserInfo) { + return Promise.reject("Test requires an admin user"); + } + return Realm.Sync.User.login('http://localhost:9080', global.testAdminUserInfo.username, global.testAdminUserInfo.password) + .then(user => { TestCase.assertTrue(user.isAdmin, "Test requires an admin user"); - - user.retrieveAccount('password', global.testAdminUserInfo.username) - .then(account => { - // { - // "provider_id": "admin", - // "provider": "password", - // "user": { - // "id": "07ac9a0a-a97a-4ee1-b53c-b05a6542035a", - // "isAdmin": true, - // } - // } - - TestCase.assertEqual(account.provider_id, global.testAdminUserInfo.username); - TestCase.assertEqual(account.provider, 'password'); - TestCase.assertTrue(account.user); - TestCase.assertTrue(account.user.isAdmin !== undefined); - TestCase.assertTrue(account.user.id); - resolve(); - }) - .catch(e => reject(e)); + return user.retrieveAccount() }) - }); + .then(account => { + // { + // "provider_id": "admin", + // "provider": "password", + // "user": { + // "id": "07ac9a0a-a97a-4ee1-b53c-b05a6542035a", + // "isAdmin": true, + // } + // } + TestCase.assertEqual(account.provider_id, global.testAdminUserInfo.username); + TestCase.assertEqual(account.provider, 'password'); + TestCase.assertTrue(account.user); + TestCase.assertTrue(account.user.isAdmin !== undefined); + TestCase.assertTrue(account.user.id); + }) + .catch(e => reject(e)); }, testRetrieveNotExistingAccount() { - return new Promise((resolve, reject) => { - if (!isNodeProcess) { - resolve(); - } - - if (!global.testAdminUserInfo) { - reject("Test requires an admin user"); - } - - Realm.Sync.User.login('http://localhost:9080', global.testAdminUserInfo.username, global.testAdminUserInfo.password, (error, user) => { - if (error) { - reject(error); - } - + if (!isNodeProcess) { + return Promise.resolve() + } + if (!global.testAdminUserInfo) { + return Promise.reject("Test requires an admin user"); + } + return Realm.Sync.User.login('http://localhost:9080', global.testAdminUserInfo.username, global.testAdminUserInfo.password) + .then(user => { TestCase.assertTrue(user.isAdmin, "Test requires an admin user"); - let notExistingUsername = uuid(); - user.retrieveAccount('password', notExistingUsername) - .then(account => { - reject("Retrieving not existing account should fail"); - }) - .catch(e => { - TestCase.assertEqual(e.code, 404); - resolve() - }); + return user.retrieveAccount('password', notExistingUsername) }) - }); + .then(account => { + reject("Retrieving not existing account should fail"); + }) + .catch(e => { + TestCase.assertEqual(e.code, 404); + resolve() + }); }, /* This test fails because of realm-object-store #243 . We should use 2 users. From 1e26717d3f783a0a65c75648574e78faaceceb96 Mon Sep 17 00:00:00 2001 From: Maximilian Alexander Date: Sun, 27 Aug 2017 09:18:41 -0700 Subject: [PATCH 02/14] Removing duplicate for changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f2538f8..a3237ad8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ X.Y.Z Release notes * Added additional parameter for `Realm.open` and `Realm.openAsync` for download progress notifications * Added `Realm.deleteFile` for deleting a Realm (#363). * Added `Realm.deleteModel` for deleting a Realm model in a migration (#573). +* `Realm.Sync.User.login`, `Realm.Sync.User.register`, and `Realm.Sync.User.registerWithProvider` return Promises and deprecate the callback style for them. Callbacks will continue to work for backward compatibility. ### Bug fixes * Adding missing TypeScript definitions; Permissions (#1283) and `setFeatureToken()`. From a51ea7215175b428906dd358e92e714bcf115ed5 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 12 Sep 2017 20:23:49 +0300 Subject: [PATCH 03/14] Fix changelog.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3237ad8..4f0d081d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ X.Y.Z Release notes ### Bug fixes * Adding missing TypeScript definitions; Permissions (#1283) and `setFeatureToken()`. +* Removed `loginWithProvider` from TypeScript definition files. This API never existed and was incorrectly added. 1.11.1 Release notes (2017-9-1) ============================================================= @@ -38,7 +39,7 @@ X.Y.Z Release notes * Added various methods for permission management (#1204). ### Bug fixes -* Removed `loginWithProvider` from TypeScript definition files. This API never existed and was incorrectly added. +* None 1.10.3 Release notes (2017-8-16) From 3fbd1291e134ae39d9bf7fc45076eab394f1fd4f Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 12 Sep 2017 20:26:08 +0300 Subject: [PATCH 04/14] fix _authenticate call to always pass the json object --- lib/user-methods.js | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/lib/user-methods.js b/lib/user-methods.js index 0b2d9d4e..16335c39 100644 --- a/lib/user-methods.js +++ b/lib/user-methods.js @@ -152,7 +152,7 @@ function _authenticate(userConstructor, server, json, callback) { callback(null, user); }) .catch(err => { - callback(err) + callback(err); }); } else { return promise; @@ -188,11 +188,9 @@ const staticMethods = { user_info: { password: password, register: true }, data: username }; - if (callback) { - _authenticate(this, server, json, callback); - } else { - return _authenticate(this, server) - } + + _authenticate(this, server, json, callback); + }, login(server, username, password, callback) { @@ -202,11 +200,8 @@ const staticMethods = { user_info: { password: password }, data: username }; - if (callback) { - _authenticate(this, server, json, callback); - } else { - return _authenticate(this, server) - } + + _authenticate(this, server, json, callback); }, registerWithProvider(server, options, callback) { @@ -233,11 +228,7 @@ const staticMethods = { json.user_info = options.userInfo; } - if (callback) { - _authenticate(this, server, json, callback); - } else { - return _authenticate(this, server) - } + _authenticate(this, server, json, callback); }, _refreshAccessToken: refreshAccessToken From 115a2c99023a30060145f88040d1c1a2a25c29fb Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 12 Sep 2017 20:38:43 +0300 Subject: [PATCH 05/14] add deprecated warnings --- lib/index.d.ts | 20 +++++++++++++++++--- lib/user-methods.js | 12 ++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index 242174eb..5066dbb2 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -264,12 +264,25 @@ declare namespace Realm.Sync { readonly server: string; readonly token: string; static adminUser(adminToken: string, server?: string): User; - static login(server: string, username: string, password: string): Promise; + + /** + * @deprecated, to be removed in future versions + */ static login(server: string, username: string, password: string, callback: (error: any, user: User) => void): void; - static register(server: string, username: string, password: string): Promise; + static login(server: string, username: string, password: string): Promise; + + /** + * @deprecated, to be removed in future versions + */ static register(server: string, username: string, password: string, callback: (error: any, user: User) => void): void; - static registerWithProvider(server: string, options: { provider: string, providerToken: string, userInfo: any }): Promise; + static register(server: string, username: string, password: string): Promise; + + /** + * @deprecated, to be removed in versions + */ static registerWithProvider(server: string, options: { provider: string, providerToken: string, userInfo: any }, callback: (error: Error | null, user: User | null) => void): void; + static registerWithProvider(server: string, options: { provider: string, providerToken: string, userInfo: any }): Promise; + logout(): void; openManagementRealm(): Realm; retrieveAccount(provider: string, username: string): Promise; @@ -377,6 +390,7 @@ declare namespace Realm.Sync { function removeListener(regex: string, name: string, changeCallback: (changeEvent: ChangeEvent) => void): void; function setLogLevel(logLevel: 'all' | 'trace' | 'debug' | 'detail' | 'info' | 'warn' | 'error' | 'fatal' | 'off'): void; function setFeatureToken(token: string): void; + /** * @deprecated, to be removed in 2.0 */ diff --git a/lib/user-methods.js b/lib/user-methods.js index 16335c39..4035bc85 100644 --- a/lib/user-methods.js +++ b/lib/user-methods.js @@ -189,6 +189,10 @@ const staticMethods = { data: username }; + if (callback) { + const message = "register(..., callback) is now deprecated in favor of register(): Promise. This function argument will be removed in future versions."; + (console.warn || console.log).call(console, message); + } _authenticate(this, server, json, callback); }, @@ -201,6 +205,10 @@ const staticMethods = { data: username }; + if (callback) { + const message = "login(..., callback) is now deprecated in favor of login(): Promise. This function argument will be removed in future versions."; + (console.warn || console.log).call(console, message); + } _authenticate(this, server, json, callback); }, @@ -228,6 +236,10 @@ const staticMethods = { json.user_info = options.userInfo; } + if (callback) { + const message = "registerWithProvider(..., callback) is now deprecated in favor of registerWithProvider(): Promise. This function argument will be removed in future versions."; + (console.warn || console.log).call(console, message); + } _authenticate(this, server, json, callback); }, From 9366de14ef3bdef8ec3102885537397b66947e5a Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 12 Sep 2017 23:01:58 +0300 Subject: [PATCH 06/14] Revert user-tests to still use callbacks for login/register etc --- tests/js/user-tests.js | 100 ++++++++++++++++++++++++----------------- 1 file changed, 59 insertions(+), 41 deletions(-) diff --git a/tests/js/user-tests.js b/tests/js/user-tests.js index 466f80ca..4d2a24ab 100644 --- a/tests/js/user-tests.js +++ b/tests/js/user-tests.js @@ -297,55 +297,73 @@ module.exports = { }, testRetrieveAccount() { - if (!isNodeProcess) { - return Promise.resolve() - } - if (!global.testAdminUserInfo) { - return Promise.reject("Test requires an admin user"); - } - return Realm.Sync.User.login('http://localhost:9080', global.testAdminUserInfo.username, global.testAdminUserInfo.password) - .then(user => { + return new Promise((resolve, reject) => { + if (!isNodeProcess) { + resolve(); + } + + if (!global.testAdminUserInfo) { + reject("Test requires an admin user"); + } + + Realm.Sync.User.login('http://localhost:9080', global.testAdminUserInfo.username, global.testAdminUserInfo.password, (error, user) => { + if (error) { + reject(error); + } + TestCase.assertTrue(user.isAdmin, "Test requires an admin user"); - return user.retrieveAccount() + + user.retrieveAccount('password', global.testAdminUserInfo.username) + .then(account => { + // { + // "provider_id": "admin", + // "provider": "password", + // "user": { + // "id": "07ac9a0a-a97a-4ee1-b53c-b05a6542035a", + // "isAdmin": true, + // } + // } + + TestCase.assertEqual(account.provider_id, global.testAdminUserInfo.username); + TestCase.assertEqual(account.provider, 'password'); + TestCase.assertTrue(account.user); + TestCase.assertTrue(account.user.isAdmin !== undefined); + TestCase.assertTrue(account.user.id); + resolve(); + }) + .catch(e => reject(e)); }) - .then(account => { - // { - // "provider_id": "admin", - // "provider": "password", - // "user": { - // "id": "07ac9a0a-a97a-4ee1-b53c-b05a6542035a", - // "isAdmin": true, - // } - // } - TestCase.assertEqual(account.provider_id, global.testAdminUserInfo.username); - TestCase.assertEqual(account.provider, 'password'); - TestCase.assertTrue(account.user); - TestCase.assertTrue(account.user.isAdmin !== undefined); - TestCase.assertTrue(account.user.id); - }) - .catch(e => reject(e)); + }); }, testRetrieveNotExistingAccount() { - if (!isNodeProcess) { - return Promise.resolve() - } - if (!global.testAdminUserInfo) { - return Promise.reject("Test requires an admin user"); - } - return Realm.Sync.User.login('http://localhost:9080', global.testAdminUserInfo.username, global.testAdminUserInfo.password) - .then(user => { + return new Promise((resolve, reject) => { + if (!isNodeProcess) { + resolve(); + } + + if (!global.testAdminUserInfo) { + reject("Test requires an admin user"); + } + + Realm.Sync.User.login('http://localhost:9080', global.testAdminUserInfo.username, global.testAdminUserInfo.password, (error, user) => { + if (error) { + reject(error); + } + TestCase.assertTrue(user.isAdmin, "Test requires an admin user"); + let notExistingUsername = uuid(); - return user.retrieveAccount('password', notExistingUsername) + user.retrieveAccount('password', notExistingUsername) + .then(account => { + reject("Retrieving not existing account should fail"); + }) + .catch(e => { + TestCase.assertEqual(e.code, 404); + resolve() + }); }) - .then(account => { - reject("Retrieving not existing account should fail"); - }) - .catch(e => { - TestCase.assertEqual(e.code, 404); - resolve() - }); + }); }, /* This test fails because of realm-object-store #243 . We should use 2 users. From eba51066d9d9732a485674833b6dd561f477c647 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 12 Sep 2017 23:02:14 +0300 Subject: [PATCH 07/14] fix AuthError object --- lib/errors.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/errors.js b/lib/errors.js index 82767ca2..f2c55fe2 100644 --- a/lib/errors.js +++ b/lib/errors.js @@ -19,7 +19,12 @@ 'use strict'; function AuthError(problem) { - Error.call(this, problem.title); + const error = Error.call(this, problem.title); + + this.name = 'AuthError'; + this.message = error.message; + this.stack = error.stack; + Object.assign(this, problem); } From ba9321834ba8dfcb5c37957753f0c5818d43b452 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 12 Sep 2017 23:04:20 +0300 Subject: [PATCH 08/14] Fix _authenticate method to use promises only --- lib/user-methods.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/user-methods.js b/lib/user-methods.js index 4035bc85..d4cbe209 100644 --- a/lib/user-methods.js +++ b/lib/user-methods.js @@ -132,18 +132,19 @@ function _authenticate(userConstructor, server, json, callback) { headers: postHeaders, open_timeout: 5000 }; + const promise = performFetch(url, options) .then((response) => { if (response.status !== 200) { - return response.json().then((body) => callback(new AuthError(body))); + return response.json().then((body) => Promise.reject(new AuthError(body))); } else { return response.json().then(function (body) { // TODO: validate JSON const token = body.refresh_token.token; const identity = body.refresh_token.token_data.identity; const isAdmin = body.refresh_token.token_data.is_admin; - callback(undefined, userConstructor.createUser(server, identity, token, false, isAdmin)); - }) + return userConstructor.createUser(server, identity, token, false, isAdmin); + }); } }); @@ -193,8 +194,8 @@ const staticMethods = { const message = "register(..., callback) is now deprecated in favor of register(): Promise. This function argument will be removed in future versions."; (console.warn || console.log).call(console, message); } - _authenticate(this, server, json, callback); - + + return _authenticate(this, server, json, callback); }, login(server, username, password, callback) { @@ -209,7 +210,8 @@ const staticMethods = { const message = "login(..., callback) is now deprecated in favor of login(): Promise. This function argument will be removed in future versions."; (console.warn || console.log).call(console, message); } - _authenticate(this, server, json, callback); + + return _authenticate(this, server, json, callback); }, registerWithProvider(server, options, callback) { @@ -240,7 +242,8 @@ const staticMethods = { const message = "registerWithProvider(..., callback) is now deprecated in favor of registerWithProvider(): Promise. This function argument will be removed in future versions."; (console.warn || console.log).call(console, message); } - _authenticate(this, server, json, callback); + + return _authenticate(this, server, json, callback); }, _refreshAccessToken: refreshAccessToken From b58e9094da73a7eff05a48d498bc3da5be429579 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 12 Sep 2017 23:04:46 +0300 Subject: [PATCH 09/14] output the error as it is in download-api-helper --- tests/js/download-api-helper.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/js/download-api-helper.js b/tests/js/download-api-helper.js index 5b6ac165..5b9e6c4b 100644 --- a/tests/js/download-api-helper.js +++ b/tests/js/download-api-helper.js @@ -36,7 +36,7 @@ Realm.Sync.User.register('http://localhost:9080', username, 'password', (error, Realm.Sync.User.login('http://localhost:9080', username, 'password', (err, loggedUser) => { if (err) { const loginError = JSON.stringify(err); - console.error("download-api-helper failed:\n User.register() error:\n" + registrationError + "\n User.login() error:\n" + loginError); + console.error("download-api-helper failed:\n User.register() error:\n" + err + "\n" + registrationError + "\n User.login() error:\n" + loginError); process.exit(-2); } else { From 1f5b98bb53b116c6cf7d04b614e548c865d65399 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 12 Sep 2017 23:08:54 +0300 Subject: [PATCH 10/14] Use promisified methods for login and register to have backward compatibility testing as well --- tests/js/session-tests.js | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/tests/js/session-tests.js b/tests/js/session-tests.js index 6f61bcfd..8157a9eb 100644 --- a/tests/js/session-tests.js +++ b/tests/js/session-tests.js @@ -50,6 +50,32 @@ function uuid() { }); } +function promisifiedRegister(server, username, password) { + return new Promise((resolve, reject) => { + Realm.Sync.User.register(server, username, password, (error, user) => { + if (error) { + console.log(`promisifiedRegister ${error}`); + reject(error); + } else { + resolve(user); + } + }); + }); +} + +function promisifiedLogin(server, username, password) { + return new Promise((resolve, reject) => { + Realm.Sync.User.login(server, username, password, (error, user) => { + if (error) { + console.log(`promisifiedLogin ${error}`); + reject(error); + } else { + resolve(user); + } + }); + }); +} + function runOutOfProcess(nodeJsFilePath) { var nodeArgs = Array.prototype.slice.call(arguments); let tmpDir = tmp.dirSync(); @@ -85,7 +111,7 @@ module.exports = { }, testProperties() { - return Realm.Sync.User.login('http://localhost:9080', uuid(), 'password').then(user => { + return promisifiedRegister('http://localhost:9080', uuid(), 'password').then(user => { return new Promise((resolve, reject) => { const accessTokenRefreshed = this; @@ -136,7 +162,7 @@ module.exports = { return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { - return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { + return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { const accessTokenRefreshed = this; let successCounter = 0; @@ -446,7 +472,7 @@ module.exports = { return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { return new Promise((resolve, reject) => { let config = { sync: { @@ -481,7 +507,7 @@ module.exports = { return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { return new Promise((resolve, reject) => { let config = { sync: { @@ -548,7 +574,7 @@ module.exports = { return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { return new Promise((resolve, reject) => { let config = { sync: { @@ -587,7 +613,7 @@ module.exports = { return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH) .then(() => { - return promisifiedLogin('http://localhost:9080', username, 'password').then(user => { + return Realm.Sync.User.login('http://localhost:9080', username, 'password').then(user => { return new Promise((resolve, reject) => { let config = { sync: { From 8ba71e36c77363e63f485f7c18ec585b7dfd2c81 Mon Sep 17 00:00:00 2001 From: blagoev Date: Wed, 13 Sep 2017 12:59:13 +0300 Subject: [PATCH 11/14] add typescript definition compilation checks on every test run --- lib/index.d.ts | 2 +- tests/package.json | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/index.d.ts b/lib/index.d.ts index a49e6e6c..e1a625af 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -451,7 +451,7 @@ declare class Realm { * @param {Function} callback will be called when the realm is ready. * @param {ProgressNotificationCallback} progressCallback? a progress notification callback for 'download' direction and 'forCurrentlyOutstandingWork' mode */ - static openAsync(config: Realm.Configuration, callback: (error: any, realm: Realm) => void, progressCallback?: Realm.Sync.ProgressNotificationCallback): void + static openAsync(config: Realm.Configuration, hello?: string, callback: (error: any, realm: Realm) => void, progressCallback?: Realm.Sync.ProgressNotificationCallback): void /** * Delete the Realm file for the given configuration. diff --git a/tests/package.json b/tests/package.json index 3919946d..b45f85f5 100644 --- a/tests/package.json +++ b/tests/package.json @@ -10,10 +10,12 @@ "needle": "^1.3.0", "terminate": "^1.0.8", "tmp": "^0.0.30", - "url-parse": "^1.1.7" + "url-parse": "^1.1.7", + "typescript": "^2.5.2" }, "scripts": { - "test": "jasmine spec/unit_tests.js", - "test-sync-integration": "jasmine spec/sync_integration_tests.js" + "check-typescript" : "tsc --noEmit --alwaysStrict ./../lib/index.d.ts", + "test": "npm run check-typescript && jasmine spec/unit_tests.js", + "test-sync-integration": "npm run check-typescript && jasmine spec/sync_integration_tests.js" } } From e48d3ae6383c542c3e343c9f36d5a94ab2fce565 Mon Sep 17 00:00:00 2001 From: blagoev Date: Wed, 13 Sep 2017 13:03:28 +0300 Subject: [PATCH 12/14] Fix for index.d.ts --- 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 e1a625af..a49e6e6c 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -451,7 +451,7 @@ declare class Realm { * @param {Function} callback will be called when the realm is ready. * @param {ProgressNotificationCallback} progressCallback? a progress notification callback for 'download' direction and 'forCurrentlyOutstandingWork' mode */ - static openAsync(config: Realm.Configuration, hello?: string, callback: (error: any, realm: Realm) => void, progressCallback?: Realm.Sync.ProgressNotificationCallback): void + static openAsync(config: Realm.Configuration, callback: (error: any, realm: Realm) => void, progressCallback?: Realm.Sync.ProgressNotificationCallback): void /** * Delete the Realm file for the given configuration. From 6cdbe503b760b5ee3c50563c18e58dd5502e71fb Mon Sep 17 00:00:00 2001 From: blagoev Date: Wed, 13 Sep 2017 13:55:27 +0300 Subject: [PATCH 13/14] fix docs --- docs/sync.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sync.js b/docs/sync.js index 609f1663..74761220 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -138,7 +138,7 @@ class User { * @param {function(error, user)} [callback] - called with the following arguments: * - `error` - an Error object is provided on failure * - `user` - a valid User object on success - * @returns {Promise} Returns a promise with a user only if the callback was not specified + * @returns {void|Promise} Returns a promise with a user if the callback was not specified */ static login(server, username, password, callback) {} @@ -149,10 +149,10 @@ class User { * @param {string} options.provider - The provider type * @param {string} options.providerToken - The access token for the given provider * @param {object} [options.userInfo] - A map containing additional data required by the provider - * @param {function(error, User)} [callback] - an optional called with the following arguments: + * @param {function(error, User)} [callback] - an optional callback called with the following arguments: * - `error` - an Error object is provided on failure * - `user` - a valid User object on success - * @return {Promise} Returns a promise with a user only if the callback was not specified + * @return {void|Promise} Returns a promise with a user if the callback was not specified */ static registerWithProvider(server, options, callback) {} @@ -164,7 +164,7 @@ class User { * @param {function(error, user)} [callback] - called with the following arguments: * - `error` - an Error object is provided on failure * - `user` - a valid User object on success - * @return {Promise} Returns a promise with a user only if the callback was not specified + * @return {void|Promise} Returns a promise with a user if the callback was not specified */ static register(server, username, password, callback) {} From 208aa8ee46d7da0eae439aeea8050962f075448b Mon Sep 17 00:00:00 2001 From: astigsen Date: Wed, 13 Sep 2017 11:42:23 -0700 Subject: [PATCH 14/14] Added support for in-memory realms (#1304) * Added support for in-memory realms --- CHANGELOG.md | 1 + docs/realm.js | 5 +++++ lib/browser/index.js | 1 + lib/index.d.ts | 1 + src/js_realm.hpp | 13 +++++++++++++ tests/js/realm-tests.js | 30 ++++++++++++++++++++++++++++++ 6 files changed, 51 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0bdfb8f..173931a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ X.Y.Z Release notes * Added additional parameter for `Realm.open` and `Realm.openAsync` for download progress notifications * Added `Realm.deleteFile` for deleting a Realm (#363). * Added `Realm.deleteModel` for deleting a Realm model in a migration (#573). +* Added support for in-memory Realms. * `Realm.Sync.User.login`, `Realm.Sync.User.register`, and `Realm.Sync.User.registerWithProvider` return Promises and deprecate the callback style for them. Callbacks will continue to work for backward compatibility. ### Bug fixes diff --git a/docs/realm.js b/docs/realm.js index be25e46b..b6055106 100644 --- a/docs/realm.js +++ b/docs/realm.js @@ -278,6 +278,11 @@ Realm.defaultPath; * 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} [inMemory=false] - Specifies if this Realm should be opened in-memory. This + * still requires a path (can be the default path) to identify the Realm so other processes can + * open the same Realm. The file will also be used as swap space if the Realm becomes bigger than + * what fits in memory, but it is not persistent and will be removed when the last instance + * is closed. * @property {boolean} [readOnly=false] - Specifies if this Realm should be opened as read-only. * @property {Array} [schema] - Specifies all the * object types in this Realm. **Required** when first creating a Realm at this `path`. diff --git a/lib/browser/index.js b/lib/browser/index.js index cb285f8e..10bc7512 100644 --- a/lib/browser/index.js +++ b/lib/browser/index.js @@ -55,6 +55,7 @@ function setupRealm(realm, realmId) { 'empty', 'path', 'readOnly', + 'inMemory', 'schema', 'schemaVersion', 'syncSession', diff --git a/lib/index.d.ts b/lib/index.d.ts index 804cdc82..19e9a085 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -80,6 +80,7 @@ declare namespace Realm { shouldCompactOnLaunch?: (totalBytes: number, usedBytes: number) => boolean; path?: string; readOnly?: boolean; + inMemory?: boolean; schema?: ObjectClass[] | ObjectSchema[]; schemaVersion?: number; sync?: Realm.Sync.SyncConfiguration; diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 6bfdb496..931b4d3d 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -187,6 +187,7 @@ public: static void get_path(ContextType, ObjectType, ReturnValue &); static void get_schema_version(ContextType, ObjectType, ReturnValue &); static void get_schema(ContextType, ObjectType, ReturnValue &); + static void get_in_memory(ContextType, ObjectType, ReturnValue &); static void get_read_only(ContextType, ObjectType, ReturnValue &); static void get_is_in_transaction(ContextType, ObjectType, ReturnValue &); #if REALM_ENABLE_SYNC @@ -243,6 +244,7 @@ public: {"path", {wrap, nullptr}}, {"schemaVersion", {wrap, nullptr}}, {"schema", {wrap, nullptr}}, + {"inMemory", {wrap, nullptr}}, {"readOnly", {wrap, nullptr}}, {"isInTransaction", {wrap, nullptr}}, #if REALM_ENABLE_SYNC @@ -380,6 +382,12 @@ void RealmClass::constructor(ContextType ctx, ObjectType this_object, size_t else if (config.path.empty()) { config.path = js::default_path(); } + + static const String in_memory_string = "inMemory"; + ValueType in_memory_value = Object::get_property(ctx, object, in_memory_string); + if (!Value::is_undefined(ctx, in_memory_value) && Value::validated_to_boolean(ctx, in_memory_value, "inMemory")) { + config.in_memory = true; + } static const String read_only_string = "readOnly"; ValueType read_only_value = Object::get_property(ctx, object, read_only_string); @@ -606,6 +614,11 @@ void RealmClass::get_schema(ContextType ctx, ObjectType object, ReturnValue & return_value.set(Schema::object_for_schema(ctx, schema)); } +template +void RealmClass::get_in_memory(ContextType ctx, ObjectType object, ReturnValue &return_value) { + return_value.set(get_internal>(object)->get()->config().in_memory); +} + template void RealmClass::get_read_only(ContextType ctx, ObjectType object, ReturnValue &return_value) { return_value.set(get_internal>(object)->get()->config().read_only()); diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index e98db623..fb52b048 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -161,6 +161,36 @@ module.exports = { }]}); }, "Property 'InvalidObject.link' declared as origin of linking objects property 'InvalidObject.linkingObjects' links to type 'IntObject'") }, + + testRealmConstructorInMemory: function() { + // open in-memory realm instance + const realm1 = new Realm({inMemory: true, schema: [schemas.TestObject]}); + realm1.write(function() { + realm1.create('TestObject', [1]) + }); + TestCase.assertEqual(realm1.inMemory, true); + + // open a second instance of the same realm and check that they share data + const realm2 = new Realm({inMemory: true}); + const objects = realm2.objects('TestObject'); + TestCase.assertEqual(objects.length, 1); + TestCase.assertEqual(objects[0].doubleCol, 1.0); + TestCase.assertEqual(realm2.inMemory, true); + + // Close both realms (this should delete the realm since there are no more + // references to it. + realm1.close(); + realm2.close(); + + // Open the same in-memory realm again and verify that it is now empty + const realm3 = new Realm({inMemory: true}); + TestCase.assertEqual(realm3.schema.length, 0); + + // try to open the same realm in persistent mode (should fail as you cannot mix modes) + TestCase.assertThrows(function() { + const realm4 = new Realm({}); + }); + }, testRealmConstructorReadOnly: function() { var realm = new Realm({schema: [schemas.TestObject]});