Merge branch 'master' of github.com:realm/realm-js into 2.0.x

This commit is contained in:
Kenneth Geisshirt 2017-09-08 14:54:56 +02:00
commit fa0016ba6b
32 changed files with 1203 additions and 114 deletions

View File

@ -1,3 +1,42 @@
X.Y.Z Release notes
=============================================================
### Breaking changes
* None
### Enhancements
* Improve performance of the RPC worker for chrome debugging.
* Added `Realm.deleteFile` for deleting a Realm (#363).
### Bug fixes
* Adding missing TypeScript declation (#1283).
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
* 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.
* Added various methods for permission management (#1204).
### Bug fixes
* None
1.10.3 Release notes (2017-8-16)
=============================================================
### Breaking changes
@ -7,7 +46,7 @@
* None
### Bug fixes
* none
* None
1.10.2 Release notes (2017-8-16)

View File

@ -38,3 +38,53 @@ 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 Foundations 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 <help@realm.io>.
### 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<typename T>
void RealmClass<T>::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<T, RealmClass<T>>(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.
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<T> const methods` declaration in `src/js_realm.hpp` and add your function to it:
```
{"crashOnStart", wrap<crashOnStart>},
```
#### 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

20
Jenkinsfile vendored
View File

@ -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,23 +188,9 @@ 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=realm'

View File

@ -26,17 +26,17 @@ 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
In case you don't want to use the precompiled version on npm, you can build Realm yourself from source. Youll 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:

View File

@ -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}
@ -182,6 +190,40 @@ class Realm {
* @param {function()} callback
*/
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.
*
* 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() {}
}
/**
@ -195,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}
@ -213,6 +262,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.

View File

@ -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) { }
}
/**

View File

@ -58,6 +58,7 @@ function setupRealm(realm, realmId) {
'schema',
'schemaVersion',
'syncSession',
'isInTransaction',
].forEach((name) => {
Object.defineProperty(realm, name, {get: util.getterForProperty(name)});
});
@ -132,6 +133,10 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [
'delete',
'deleteAll',
'write',
'compact',
'beginTransaction',
'commitTransaction',
'cancelTransaction',
], true);
const Sync = {
@ -164,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', []);

View File

@ -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");
}

82
lib/index.d.ts vendored
View File

@ -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[];
@ -270,6 +271,58 @@ declare namespace Realm.Sync {
logout(): void;
openManagementRealm(): Realm;
retrieveAccount(provider: string, username: string): Promise<Account>;
getGrantedPermissions(recipient: 'any' | 'currentUser' | 'otherUser'): Results<Permission>;
applyPermissions(condition: PermissionCondition, realmUrl: string, accessLevel: AccessLevel): Promise<PermissionChange>;
offerPermissions(realmUrl: string, accessLevel: AccessLevel, expiresAt?: Date): Promise<string>;
acceptPermissionOffer(token: string): Promise<string>
invalidatePermissionOffer(permissionOfferOrToken: PermissionOffer | string): Promise<void>;
}
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;
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 {
@ -313,7 +366,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 = {
@ -357,6 +410,7 @@ declare class Realm {
readonly readOnly: boolean;
readonly schema: Realm.ObjectSchema[];
readonly schemaVersion: number;
readonly isInTransaction: boolean;
readonly syncSession: Realm.Sync.Session | null;
@ -380,6 +434,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?
*/
@ -452,6 +512,26 @@ declare class Realm {
* @returns void
*/
write(callback: () => void): void;
/**
* @returns void
*/
beginTransaction(): void;
/**
* @returns void
*/
commitTransaction(): void;
/**
* @returns void
*/
cancelTransaction(): void;
/**
* @returns boolean
*/
compact(): boolean;
}
declare module 'realm' {

View File

@ -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 },

274
lib/permission-api.js Normal file
View File

@ -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();
});
});
}
}

View File

@ -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 = {
}
});
},
},
};
// Append the permission apis
Object.assign(instanceMethods, permissionApis);
module.exports = {
static: staticMethods,
instance: instanceMethods
};

110
package-lock.json generated
View File

@ -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"
}

View File

@ -1,7 +1,11 @@
{
"name": "realm",
"description": "Realm is a mobile database: an alternative to SQLite and key-value stores",
<<<<<<< HEAD
"version": "2.0.0-rc5",
=======
"version": "1.11.1",
>>>>>>> 38bceddfb19fd9f8ac5cc62ddb8bfa626270aa5c
"license": "Apache-2.0",
"homepage": "https://realm.io",
"keywords": [

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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!"

View File

@ -396,6 +396,17 @@ case "$TARGET" in
npm install --build-from-source=realm
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" .

View File

@ -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());
}
}

View File

@ -20,37 +20,26 @@
#include <condition_variable>
#include <deque>
#include <exception>
#include <mutex>
#include <realm/util/optional.hpp>
namespace realm {
class ConcurrentDequeTimeout : public std::exception {
public:
ConcurrentDequeTimeout() : std::exception() {}
};
template <typename T>
class ConcurrentDeque {
public:
T pop_front(size_t timeout = 0) {
public:
T pop_back() {
std::unique_lock<std::mutex> 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<T> try_pop_back(size_t timeout) {
std::unique_lock<std::mutex> 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<std::mutex> &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<T> m_deque;
T do_pop_back() {
T item = std::move(m_deque.back());
m_deque.pop_back();
return item;
}
};
} // realm

View File

@ -102,4 +102,19 @@ 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
}
}

View File

@ -171,11 +171,16 @@ 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 &);
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 &);
@ -183,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
@ -194,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 &);
@ -205,6 +212,7 @@ public:
{"schemaVersion", wrap<schema_version>},
{"clearTestState", wrap<clear_test_state>},
{"copyBundledRealmFiles", wrap<copy_bundled_realm_files>},
{"deleteFile", wrap<delete_file>},
{"_waitForDownload", wrap<wait_for_download_completion>},
};
@ -219,10 +227,14 @@ public:
{"delete", wrap<delete_one>},
{"deleteAll", wrap<delete_all>},
{"write", wrap<write>},
{"beginTransaction", wrap<begin_transaction>},
{"commitTransaction", wrap<commit_transaction>},
{"cancelTransaction", wrap<cancel_transaction>},
{"addListener", wrap<add_listener>},
{"removeListener", wrap<remove_listener>},
{"removeAllListeners", wrap<remove_all_listeners>},
{"close", wrap<close>},
{"compact", wrap<compact>},
};
PropertyMap<T> const properties = {
@ -231,6 +243,7 @@ public:
{"schemaVersion", {wrap<get_schema_version>, nullptr}},
{"schema", {wrap<get_schema>, nullptr}},
{"readOnly", {wrap<get_read_only>, nullptr}},
{"isInTransaction", {wrap<get_is_in_transaction>, nullptr}},
#if REALM_ENABLE_SYNC
{"syncSession", {wrap<get_sync_session>, nullptr}},
#endif
@ -390,6 +403,28 @@ void RealmClass<T>::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<T>::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)) {
@ -493,6 +528,37 @@ void RealmClass<T>::copy_bundled_realm_files(ContextType ctx, FunctionType, Obje
realm::copy_bundled_realm_files();
}
template<typename T>
void RealmClass<T>::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<typename T>
void RealmClass<T>::get_default_path(ContextType ctx, ObjectType object, ReturnValue &return_value) {
return_value.set(realm::js::default_path());
@ -533,6 +599,11 @@ void RealmClass<T>::get_read_only(ContextType ctx, ObjectType object, ReturnValu
return_value.set(get_internal<T, RealmClass<T>>(object)->get()->config().read_only());
}
template<typename T>
void RealmClass<T>::get_is_in_transaction(ContextType ctx, ObjectType object, ReturnValue &return_value) {
return_value.set(get_internal<T, RealmClass<T>>(object)->get()->is_in_transaction());
}
#if REALM_ENABLE_SYNC
template<typename T>
void RealmClass<T>::get_sync_session(ContextType ctx, ObjectType object, ReturnValue &return_value) {
@ -784,6 +855,30 @@ void RealmClass<T>::write(ContextType ctx, FunctionType, ObjectType this_object,
realm->commit_transaction();
}
template<typename T>
void RealmClass<T>::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<T, RealmClass<T>>(this_object);
realm->begin_transaction();
}
template<typename T>
void RealmClass<T>::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<T, RealmClass<T>>(this_object);
realm->commit_transaction();
}
template<typename T>
void RealmClass<T>::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<T, RealmClass<T>>(this_object);
realm->cancel_transaction();
}
template<typename T>
void RealmClass<T>::add_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 2);
@ -834,5 +929,17 @@ void RealmClass<T>::close(ContextType ctx, FunctionType, ObjectType this_object,
realm->close();
}
template<typename T>
void RealmClass<T>::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<T, RealmClass<T>>(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

View File

@ -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<uv_errno_t>(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<uv_errno_t>(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<uv_errno_t>(delete_req.result));
}
}
FileSystemRequest rmdir_req;
if (uv_fs_rmdir(uv_default_loop(), &rmdir_req, path.c_str(), nullptr)) {
throw UVException(static_cast<uv_errno_t>(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<uv_errno_t>(exists_req.result));
}
}
FileSystemRequest delete_req;
if (uv_fs_unlink(uv_default_loop(), &delete_req, path.c_str(), nullptr) != 0) {
throw UVException(static_cast<uv_errno_t>(delete_req.result));
}
}
} // realm

View File

@ -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);
}

View File

@ -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() {

View File

@ -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); }

View File

@ -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);
}
});
});
});
},
}

View File

@ -943,5 +943,128 @@ module.exports = {
realm.write(() => realm.delete(realm.objects('PersonObject')));
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;
// 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());
},
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();
}
};

View File

@ -119,6 +119,13 @@ exports.StringPrimary = {
}
};
exports.StringOnly = {
name: 'StringOnlyObject',
properties: {
stringCol: 'string',
}
};
exports.AllTypes = {
name: 'AllTypesObject',
primaryKey: 'primaryCol',

View File

@ -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++;
}
}