Merge pull request #1325 from realm/tg/merge-master-to-next-major

Merge latest master to next-major
This commit is contained in:
Thomas Goyne 2017-09-20 07:55:24 -07:00 committed by GitHub
commit e42db5aba2
54 changed files with 2518 additions and 680 deletions

1
.vscode/launch.json vendored
View File

@ -54,6 +54,7 @@
"type": "node",
"request": "attach",
"name": "Attach to Port",
"protocol": "legacy",
"address": "localhost",
"port": 5858
}

View File

@ -1,3 +1,48 @@
1.12.0 Release notes (2017-9-14)
=============================================================
### Breaking changes
* None
### Enhancements
* Improve performance of the RPC worker for chrome debugging.
* Added Progress API `realm.syncSession.addProgressNotification` and `realm.syncSession.removeProgressNotification`
* 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
* Adding missing TypeScript definitions; Permissions (#1283), `setFeatureToken()`, and instructions (#1298).
* Removed `loginWithProvider` from TypeScript definition files. This API never existed and was incorrectly added.
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 +52,7 @@
* None
### Bug fixes
* none
* None
1.10.2 Release notes (2017-8-16)

View File

@ -38,3 +38,54 @@ 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
* Add an entry to `CHANGELOG.md` if applicable (Breaking changes/Enhancements/Bug fixes)

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}
@ -87,7 +95,7 @@ class Realm {
* Open a realm asynchronously with a promise. If the realm is synced, it will be fully
* synchronized before it is available.
* @param {Realm~Configuration} config
* @returns {Promise} - a promise that will be resolved with the realm instance when it's available.
* @returns {ProgressPromise} - a promise that will be resolved with the realm instance when it's available.
*/
static open(config) {}
@ -96,9 +104,10 @@ class Realm {
* synchronized before it is available.
* @param {Realm~Configuration} config
* @param {callback(error, realm)} - will be called when the realm is ready.
* @param {callback(transferred, transferable)} [progressCallback] - an optional callback for download progress notifications
* @throws {Error} If anything in the provided `config` is invalid.
*/
static openAsync(config, callback) {}
static openAsync(config, callback, progressCallback) {}
/**
* Closes this Realm so it may be re-opened with a newer schema version.
@ -124,6 +133,12 @@ class Realm {
*/
delete(object) {}
/**
* Deletes a Realm model, including all of its objects.
* @param {string} name - the model name
*/
deleteModel(name) {}
/**
* **WARNING:** This will delete **all** objects in the Realm!
*/
@ -182,6 +197,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 +244,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,8 +269,20 @@ 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} [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<Realm~ObjectClass|Realm~ObjectSchema>} [schema] - Specifies all the
* object types in this Realm. **Required** when first creating a Realm at this `path`.
@ -223,7 +291,11 @@ Realm.defaultPath;
* @property {Object} [sync] - Sync configuration parameters with the following
* child properties:
* - `user` - A `User` object obtained by calling `Realm.Sync.User.login`
* - `url` - A `string` which contains a valid Realm Sync url
* - `url` - A `string` which contains a valid Realm Sync url
* - `error` - A callback function which is called in error situations
* - `validate_ssl` - Indicating if SSL certificates must be validated
* - `ssl_trust_certificate_path` - A path where to find trusted SSL certificates
* The `error` callback can take up to four optional arguments: `message`, `isFatal`, `category`, and `code`.
*/
/**

View File

@ -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 {void|Promise<User>} Returns a promise with a user 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 callback called with the following arguments:
* - `error` - an Error object is provided on failure
* - `user` - a valid User object on success
* @return {void|Promise<User>} Returns a promise with a user 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 {void|Promise<User>} Returns a promise with a user if the callback was not specified
*/
static register(server, username, password, callback) {}
@ -242,6 +245,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) { }
}
/**
@ -280,6 +337,28 @@ class Session {
* @type {string}
*/
get state() {}
/**
* Register a progress notification callback on a session object
* @param {string} direction - The progress direction to register for.
* Can be either:
* - `download` - report download progress
* - `upload` - report upload progress
* @param {string} mode - The progress notification mode to use for the registration.
* Can be either:
* - `reportIndefinitely` - the registration will stay active until the callback is unregistered
* - `forCurrentlyOutstandingWork` - the registration will be active until only the currently transferable bytes are synced
* @param {callback(transferred, transferable)} callback - called with the following arguments:
* - `transferred` - the current number of bytes already transferred
* - `transferable` - the total number of transferable bytes (the number of bytes already transferred plus the number of bytes pending transfer)
*/
addProgressNotification(direction, mode, progressCallback) {}
/** Unregister a progress notification callback that was previously registered with addProgressNotification.
* Calling the function multiple times with the same callback is ignored.
* @param {callback(transferred, transferable)} callback - a previously registered progress callback
*/
removeProgressNotification(progressCallback) {}
}

View File

@ -55,9 +55,11 @@ function setupRealm(realm, realmId) {
'empty',
'path',
'readOnly',
'inMemory',
'schema',
'schemaVersion',
'syncSession',
'isInTransaction',
].forEach((name) => {
Object.defineProperty(realm, name, {get: util.getterForProperty(name)});
});
@ -81,14 +83,14 @@ export default class Realm {
if (typeof item == 'function') {
let schema = item.schema;
if (!schema || typeof schema != 'object') {
throw new Error("Realm object constructor must have 'schema' property");
throw new Error("Realm object constructor must have a 'schema' property.");
}
let {name, properties} = schema;
if (!name || typeof name != 'string') {
throw new Error("Realm object schema must have 'name' property");
throw new Error(`Failed to read ObjectSchema: name must be of type 'string', got (${typeof name})`);
} else if (!properties || typeof properties != 'object') {
throw new Error("Realm object schema must have 'properties' property");
throw new Error(`Failed to read ObjectSchema: properties must be of type 'object', got (${typeof properties})`);
}
schemas.splice(i, 1, schema);
@ -130,8 +132,13 @@ util.createMethods(Realm.prototype, objectTypes.REALM, [
// Mutating methods:
util.createMethods(Realm.prototype, objectTypes.REALM, [
'delete',
'deleteModel',
'deleteAll',
'write',
'compact',
'beginTransaction',
'commitTransaction',
'cancelTransaction',
], true);
const Sync = {
@ -164,6 +171,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', []);
@ -177,7 +189,8 @@ Object.defineProperties(Realm, {
},
},
_waitForDownload: {
value: function(_config, callback) {
value: function(_config, sessionCallback, callback) {
sessionCallback();
callback();
}
},

View File

@ -85,5 +85,5 @@ export function typeForConstructor(realmId, constructor) {
}
}
return null;
throw new Error("Constructor was not registered in the schema for this Realm")
}

View File

@ -22,7 +22,9 @@ import { keys, objectTypes } from './constants';
import { getterForProperty, createMethods } from './util';
import { deserialize } from './rpc';
export default class Session { }
export default class Session {
}
Object.defineProperties(Session.prototype, {
url: { get: getterForProperty('url') },
@ -31,7 +33,9 @@ Object.defineProperties(Session.prototype, {
createMethods(Session.prototype, objectTypes.SESSION, [
'_refreshAccessToken',
'_simulateError'
'_simulateError',
'addProgressNotification',
'removeProgressNotification'
]);
export function createSession(realmId, info) {

View File

@ -40,7 +40,7 @@ export function createMethod(type, name, mutates) {
let id = this[keys.id];
if (!realmId || !id) {
throw new TypeError(name + ' method was not called a Realm object!');
throw new TypeError(name + ' method was not called on a Realm object!');
}
if (this[keys.type] !== type) {
throw new TypeError(name + ' method was called on an object of the wrong type!');

View File

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

View File

@ -43,28 +43,52 @@ module.exports = function(realmConstructor) {
//Add async open API
Object.defineProperties(realmConstructor, getOwnPropertyDescriptors({
open(config) {
return new Promise((resolve, reject) => {
realmConstructor._waitForDownload(config, (error) => {
if (error) {
reject(error);
}
else {
try {
let syncedRealm = new this(config);
//FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented
setTimeout(() => { resolve(syncedRealm); }, 1);
} catch (e) {
reject(e);
let syncSession;
let promise = new Promise((resolve, reject) => {
realmConstructor._waitForDownload(config,
(session) => {
syncSession = session;
},
(error) => {
if (error) {
setTimeout(() => { reject(error); }, 1);
}
}
});
else {
try {
let syncedRealm = new this(config);
//FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented
setTimeout(() => { resolve(syncedRealm); }, 1);
} catch (e) {
reject(e);
}
}
});
});
promise.progress = (callback) => {
if (syncSession) {
syncSession.addProgressNotification('download', 'forCurrentlyOutstandingWork', callback);
}
return promise;
};
return promise;
},
openAsync(config, callback) {
realmConstructor._waitForDownload(config, (error) => {
openAsync(config, callback, progressCallback) {
const message = "Realm.openAsync is now deprecated in favor of Realm.open. This function will be removed in future versions.";
(console.warn || console.log).call(console, message);
realmConstructor._waitForDownload(config,
(syncSession) => {
if (progressCallback) {
syncSession.addProgressNotification('download', 'forCurrentlyOutstandingWork', progressCallback);
}
},
(error) => {
if (error) {
callback(error);
setTimeout(() => { callback(error); }, 1);
}
else {
try {
@ -72,7 +96,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);
}
}
});
@ -107,7 +131,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");
}

135
lib/index.d.ts vendored
View File

@ -77,8 +77,10 @@ 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;
inMemory?: boolean;
schema?: ObjectClass[] | ObjectSchema[];
schemaVersion?: number;
sync?: Realm.Sync.SyncConfiguration;
@ -263,22 +265,96 @@ declare namespace Realm.Sync {
readonly server: string;
readonly token: string;
static adminUser(adminToken: string, server?: string): User;
/**
* @deprecated, to be removed in future versions
*/
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 login(server: string, username: string, password: string): Promise<Realm.Sync.User>;
/**
* @deprecated, to be removed in future versions
*/
static register(server: string, username: string, password: string, callback: (error: any, user: User) => void): void;
static register(server: string, username: string, password: string): Promise<Realm.Sync.User>;
/**
* @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<Realm.Sync.User>;
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;
}
type ErrorCallback = (message?: string, isFatal?: boolean, category?: string, code?: number) => void;
interface SyncConfiguration {
user: User;
url: string;
validate_ssl?: boolean;
ssl_trust_certificate_path?: string;
error?: ErrorCallback;
}
type ProgressNotificationCallback = (transferred: number, transferable: number) => void;
type ProgressDirection = 'download' | 'upload';
type ProgressMode = 'reportIndefinitely' | 'forCurrentlyOutstandingWork';
/**
* Session
* @see { @link https://realm.io/docs/javascript/latest/api/Realm.Sync.Session.html }
@ -288,6 +364,9 @@ declare namespace Realm.Sync {
readonly state: 'invalid' | 'active' | 'inactive';
readonly url: string;
readonly user: User;
addProgressNotification(direction: ProgressDirection, mode: ProgressMode, progressCallback: ProgressNotificationCallback): void;
removeProgressNotification(progressCallback: ProgressNotificationCallback): void;
}
/**
@ -313,11 +392,16 @@ 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 setFeatureToken(token: string): void;
/**
* @deprecated, to be removed in 2.0
*/
function setAccessToken(accessToken: string): void;
type Instruction = {
type: 'INSERT' | 'SET' | 'DELETE' | 'CLEAR' | 'LIST_SET' | 'LIST_INSERT' | 'LIST_ERASE' | 'LIST_CLEAR' | 'ADD_TYPE' | 'ADD_PROPERTIES'
type: 'INSERT' | 'SET' | 'DELETE' | 'CLEAR' | 'LIST_SET' | 'LIST_INSERT' | 'LIST_ERASE' | 'LIST_CLEAR' | 'ADD_TYPE' | 'ADD_PROPERTIES' | 'CHANGE_IDENTITY' | 'SWAP_IDENTITY'
object_type: string,
identity: string,
values: any | undefined
@ -349,6 +433,11 @@ declare namespace Realm.Sync {
}
}
interface ProgressPromise extends Promise<Realm> {
progress(callback: Realm.Sync.ProgressNotificationCallback) : Promise<Realm>
}
declare class Realm {
static defaultPath: string;
@ -357,6 +446,7 @@ declare class Realm {
readonly readOnly: boolean;
readonly schema: Realm.ObjectSchema[];
readonly schemaVersion: number;
readonly isInTransaction: boolean;
readonly syncSession: Realm.Sync.Session | null;
@ -368,17 +458,27 @@ declare class Realm {
*/
static schemaVersion(path: string, encryptionKey?: ArrayBuffer | ArrayBufferView): number;
/**
* Open a realm asynchronously with a promise. If the realm is synced, it will be fully synchronized before it is available.
* @param {Configuration} config
*/
static open(config: Realm.Configuration): Promise<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 {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): 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.
* @param {Configuration} config
*/
static deleteFile(config: Realm.Configuration): void
/**
* @param {Realm.Configuration} config?
@ -409,6 +509,11 @@ declare class Realm {
*/
delete(object: Realm.Object | Realm.Object[] | Realm.List<any> | Realm.Results<any> | any): void;
/**
* @returns void
*/
deleteModel(name: string): void;
/**
* @returns void
*/
@ -452,6 +557,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) {
setTimeout(() => reject(error), 1);
}
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) {
setTimeout(() => reject(e), 1);
}
}
});
});
}
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);
@ -27,7 +28,7 @@ function node_require(module) {
function checkTypes(args, types) {
args = Array.prototype.slice.call(args);
for (var i = 0; i < types.length; ++i) {
if (typeof args[i] !== types[i]) {
if (args.length > i && typeof args[i] !== types[i]) {
throw new TypeError('param ' + i + ' must be of type ' + types[i]);
}
}
@ -113,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);
@ -122,25 +132,35 @@ 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)));
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);
});
}
});
if (callback) {
promise.then(user => {
callback(null, user);
})
.catch(callback);
.catch(err => {
callback(err);
});
} else {
return promise;
}
}
module.exports = {
static: {
const staticMethods = {
get current() {
const allUsers = this.all;
const keys = Object.keys(allUsers);
@ -154,7 +174,7 @@ module.exports = {
},
adminUser(token, server) {
checkTypes(arguments, ['string']);
checkTypes(arguments, ['string', 'string']);
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);
@ -164,20 +184,34 @@ module.exports = {
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) {
const message = "register(..., callback) is now deprecated in favor of register(): Promise<User>. This function argument will be removed in future versions.";
(console.warn || console.log).call(console, message);
}
return _authenticate(this, server, json, callback);
},
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) {
const message = "login(..., callback) is now deprecated in favor of login(): Promise<User>. This function argument will be removed in future versions.";
(console.warn || console.log).call(console, message);
}
return _authenticate(this, server, json, callback);
},
registerWithProvider(server, options, callback) {
@ -195,21 +229,27 @@ module.exports = {
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) {
const message = "registerWithProvider(..., callback) is now deprecated in favor of registerWithProvider(): Promise<User>. This function argument will be removed in future versions.";
(console.warn || console.log).call(console, message);
}
return _authenticate(this, server, json, callback);
},
_refreshAccessToken: refreshAccessToken
},
instance: {
};
const instanceMethods = {
openManagementRealm() {
let url = url_parse(this.server);
if (url.protocol === 'http:') {
@ -232,13 +272,11 @@ module.exports = {
},
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,
@ -257,5 +295,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

@ -59,9 +59,9 @@
"jsdoc": "npm install && npm run jsdoc:clean && jsdoc -u docs/tutorials -p package.json -c docs/conf.json",
"prenode-tests": "npm install --build-from-source=realm && cd tests && npm install",
"node-tests": "cd tests && npm run test && cd ..",
"test-runner:ava": "cd tests/test-runners/ava && npm install && npm test",
"test-runner:mocha": "cd tests/test-runners/mocha && npm install && npm test",
"test-runner:jest": "cd tests/test-runners/jest && npm install && npm test",
"test-runner:ava": "cd tests/test-runners/ava && npm install --build-from-source=realm && npm test",
"test-runner:mocha": "cd tests/test-runners/mocha && npm install --build-from-source=realm && npm test",
"test-runner:jest": "cd tests/test-runners/jest && npm install --build-from-source=realm && npm test",
"test-runners": "npm run test-runner:ava && npm run test-runner:mocha && npm run test-runner:jest",
"isMac": "node -p \"if (process.platform == 'darwin') { process.exit(0); } else { process.exit(-1); }\"",
"testMac": "npm run isMac -s && echo this is mac || echo . ",
@ -86,6 +86,7 @@
"node-fetch": "^1.6.3",
"node-pre-gyp": "^0.6.36",
"progress": "^2.0.0",
"prop-types": "^15.5.10",
"request": "^2.78.0",
"stream-counter": "^1.0.0",
"sync-request": "^3.0.1",

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

@ -174,7 +174,16 @@ if (!fs.existsSync(realmDir)) {
pipeline = pipeline.then(() => decompress(downloadedArchive, targetFolder, decompressOptions));
if (extractedFolder) {
pipeline = pipeline.then(() => fs.renameSync(path.resolve(vendorDir, extractedFolder), realmDir));
pipeline = pipeline.then(() => {
fs.renameSync(path.resolve(vendorDir, extractedFolder), realmDir);
const libDir = path.resolve(realmDir, 'lib')
if (fs.existsSync(libDir)) {
// Remove all shared libraries as we want to just use the static ones
fs.readdirSync(libDir)
.filter(name => /\.so$/.test(name))
.forEach(name => fs.unlinkSync(path.resolve(libDir, name)));
}
});
}
pipeline.catch(error => {

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

@ -9,7 +9,7 @@ export NPM_CONFIG_PROGRESS=false
TARGET=$1
CONFIGURATION=${2:-Release}
if echo $CONFIGURATION | grep -i "^Debug$" > /dev/null ; then
if echo "$CONFIGURATION" | grep -i "^Debug$" > /dev/null ; then
CONFIGURATION="Debug"
fi
@ -17,8 +17,6 @@ IOS_SIM_DEVICE=${IOS_SIM_DEVICE:-} # use preferentially, otherwise will be set a
ios_sim_default_device_type=${IOS_SIM_DEVICE_TYPE:-iPhone 5s}
ios_sim_default_ios_version=${IOS_SIM_OS:-iOS 10.1}
ACCEPTED_LICENSES='MIT, ISC, BSD, Apache-2.0, BSD-2-Clause, BSD-3-Clause, WTFPL, Unlicense, (MIT AND CC-BY-3.0)'
PATH="/opt/android-sdk-linux/platform-tools:$PATH"
SRCROOT=$(cd "$(dirname "$0")/.." && pwd)
XCPRETTY=$(which xcpretty || true)
@ -48,7 +46,11 @@ download_server() {
}
start_server() {
./object-server-for-testing/start-object-server.command &
#disabled ROS logging
./object-server-for-testing/start-object-server.command &> /dev/null &
#enabled ROS logging
#./object-server-for-testing/start-object-server.command &
SERVER_PID=$!
}
@ -120,13 +122,18 @@ xctest() {
echo " done"
# - Run the build and test
xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination id="$IOS_SIM_DEVICE" build || {
EXITCODE=$?
echo "*** Failure (exit code $EXITCODE). ***"
exit $EXITCODE
}
if [ -n "$XCPRETTY" ]; then
log_temp=$(mktemp build.log.XXXXXX)
if [ -e "$log_temp" ]; then
rm "$log_temp"
fi
xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination name="iPhone 5s" build test 2>&1 | tee "$log_temp" | "$XCPRETTY" -c --no-utf --report junit --output build/reports/junit.xml || {
EXITCODE=$?
xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination name="iPhone 5s" test 2>&1 | tee "$log_temp" | "$XCPRETTY" -c --no-utf --report junit --output build/reports/junit.xml || {
EXITCODE=$?
printf "*** Xcode Failure (exit code %s). The full xcode log follows: ***\n\n" "$EXITCODE"
cat "$log_temp"
printf "\n\n*** End Xcode Failure ***\n"
@ -134,11 +141,11 @@ xctest() {
}
rm "$log_temp"
else
xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination id="$IOS_SIM_DEVICE" build test || {
EXITCODE=$?
xcrun xcodebuild -scheme "$1" -configuration "$CONFIGURATION" -sdk iphonesimulator -destination id="$IOS_SIM_DEVICE" test || {
EXITCODE=$?
echo "*** Failure (exit code $EXITCODE). ***"
exit $EXITCODE
}
}
fi
}
@ -230,14 +237,22 @@ cleanup
trap cleanup EXIT
# Use a consistent version of Node if possible.
if [ -f "$NVM_DIR/nvm.sh" ]; then
. "$NVM_DIR/nvm.sh"
elif [ -x "$(command -v brew)" ] && [ -f "$(brew --prefix nvm)/nvm.sh" ]; then
# we must be on mac and nvm was installed with brew
# TODO: change the mac slaves to use manual nvm installation
. "$(brew --prefix nvm)/nvm.sh"
if [[ -z "$(command -v nvm)" ]]; then
set +e
if [ -f "$NVM_DIR/nvm.sh" ]; then
. "$NVM_DIR/nvm.sh" '' || true
elif [ -x "$(command -v brew)" ] && [ -f "$(brew --prefix nvm)/nvm.sh" ]; then
# we must be on mac and nvm was installed with brew
# TODO: change the mac slaves to use manual nvm installation
. "$(brew --prefix nvm)/nvm.sh" '' || true
elif [ -f "$HOME/.nvm/nvm.sh" ]; then
. ~/.nvm/nvm.sh ''
fi
set -e
fi
if [[ "$(command -v nvm)" ]]; then
nvm install 6.5.0
fi
[[ "$(command -v nvm)" ]] && nvm use 6.5.0 || true
# Remove cached packages
rm -rf ~/.yarn-cache/npm-realm-*
@ -392,10 +407,19 @@ case "$TARGET" in
;;
"test-runners")
npm run check-environment
# Create a fake realm module that points to the source root so that test-runner tests can require('realm')
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

@ -101,5 +101,20 @@ void remove_realm_files_from_directory(const std::string &directory)
}
}
}
void remove_file(const std::string &path)
{
NSFileManager *manager = [NSFileManager defaultManager];
NSString *filePath = @(path.c_str());
if (![manager removeItemAtPath:filePath error:nil]) {
throw std::runtime_error((std::string)"Failed to delete file at path " + filePath.UTF8String);
}
}
void remove_directory(const std::string &path)
{
remove_file(path); // works for directories too
}
}

View File

@ -33,6 +33,29 @@ using ConstructorType = void(typename T::Context, typename T::Object, size_t, co
template<typename T>
using MethodType = void(typename T::Context, typename T::Function, typename T::Object, size_t, const typename T::Value[], ReturnValue<T> &);
template<typename T>
struct Arguments {
const typename T::Context ctx;
const size_t count;
const typename T::Value* const value;
typename T::Value operator[](size_t index) const noexcept {
if (index >= count) {
return Value<T>::from_undefined(ctx);
}
return value[index];
}
void validate_maximum(size_t max) const {
if (max < count) {
throw std::invalid_argument(util::format("Invalid arguments: at most %1 expected, but %2 supplied.", max, count));
}
}
};
template<typename T>
using ArgumentsMethodType = void(typename T::Context, typename T::Function, typename T::Object, Arguments<T>, ReturnValue<T> &);
template<typename T>
struct PropertyType {
using GetterType = void(typename T::Context, typename T::Object, ReturnValue<T> &);

View File

@ -22,8 +22,6 @@
#include "js_realm_object.hpp"
#include "js_schema.hpp"
#include "util/format.hpp"
namespace realm {
class List;
class Object;
@ -65,8 +63,9 @@ public:
ValueType value = Object::get_property(m_ctx, object, prop_name);
const auto& prop = m_object_schema.persisted_properties[prop_index];
if (!Value::is_valid_for_property(m_ctx, value, prop)) {
throw TypeErrorException(util::format("%1.%2", m_object_schema.name, prop.name),
js_type_name_for_property_type(prop.type));
throw TypeErrorException(m_object_schema.name, prop.name,
js_type_name_for_property_type(prop.type),
print(value));
}
return value;
}
@ -121,7 +120,7 @@ public:
void will_change(realm::Object&, realm::Property const&) { }
void did_change() { }
std::string print(ValueType const&) { return "not implemented"; }
std::string print(ValueType const& v) { return Value::to_string(m_ctx, v); }
private:
ContextType m_ctx;

View File

@ -123,9 +123,7 @@ class RealmDelegate : public BindingContext {
}
ObjectType realm_object = create_object<T, RealmClass<T>>(m_context, new SharedRealm(realm));
ValueType arguments[2];
arguments[0] = realm_object;
arguments[1] = Value::from_string(m_context, notification_name);
ValueType arguments[] = {realm_object, Value::from_string(m_context, notification_name)};
std::list<Protected<FunctionType>> notifications_copy(m_notifications);
for (auto &callback : notifications_copy) {
@ -148,6 +146,7 @@ class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {
using FunctionType = typename T::Function;
using ObjectType = typename T::Object;
using ValueType = typename T::Value;
using Arguments = js::Arguments<T>;
using String = js::String<T>;
using Object = js::Object<T>;
using Value = js::Value<T>;
@ -157,7 +156,7 @@ class RealmClass : public ClassDefinition<T, SharedRealm, ObservableClass<T>> {
public:
using ObjectDefaultsMap = typename Schema<T>::ObjectDefaultsMap;
using ConstructorMap = typename Schema<T>::ConstructorMap;
using WaitHandler = void(std::error_code);
using ProgressHandler = void(uint64_t transferred_bytes, uint64_t transferrable_bytes);
@ -165,24 +164,31 @@ public:
static FunctionType create_constructor(ContextType);
// methods
static void objects(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void object_for_primary_key(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void create(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
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 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 objects(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void object_for_primary_key(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void create(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_one(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_all(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void write(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void begin_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
static void commit_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
static void cancel_transaction(ContextType, FunctionType, ObjectType, Arguments, ReturnValue&);
static void add_listener(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void wait_for_download_completion(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void remove_listener(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void remove_all_listeners(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void close(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void compact(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_model(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
// properties
static void get_empty(ContextType, ObjectType, ReturnValue &);
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
static void get_sync_session(ContextType, ObjectType, ReturnValue &);
#endif
@ -191,9 +197,10 @@ public:
static void constructor(ContextType, ObjectType, size_t, const ValueType[]);
static SharedRealm create_shared_realm(ContextType, realm::Realm::Config, bool, ObjectDefaultsMap &&, ConstructorMap &&);
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 schema_version(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void clear_test_state(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void copy_bundled_realm_files(ContextType, FunctionType, ObjectType, Arguments, ReturnValue &);
static void delete_file(ContextType, FunctionType, ObjectType, Arguments, 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,15 @@ 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>},
{"deleteModel", wrap<delete_model>},
};
PropertyMap<T> const properties = {
@ -230,7 +243,9 @@ public:
{"path", {wrap<get_path>, nullptr}},
{"schemaVersion", {wrap<get_schema_version>, nullptr}},
{"schema", {wrap<get_schema>, nullptr}},
{"inMemory", {wrap<get_in_memory>, 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
@ -367,6 +382,12 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
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);
if (!Value::is_undefined(ctx, read_only_value) && Value::validated_to_boolean(ctx, read_only_value, "readOnly")) {
@ -376,7 +397,7 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
static const String schema_string = "schema";
ValueType schema_value = Object::get_property(ctx, object, schema_string);
if (!Value::is_undefined(ctx, schema_value)) {
ObjectType schema_object = Value::validated_to_object(ctx, schema_value, "schema");
ObjectType schema_object = Value::validated_to_array(ctx, schema_value, "schema");
config.schema.emplace(Schema<T>::parse_schema(ctx, schema_object, defaults, constructors));
schema_updated = true;
}
@ -390,6 +411,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)) {
@ -460,13 +503,13 @@ SharedRealm RealmClass<T>::create_shared_realm(ContextType ctx, realm::Realm::Co
}
template<typename T>
void RealmClass<T>::schema_version(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1, 2);
void RealmClass<T>::schema_version(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
realm::Realm::Config config;
config.path = normalize_realm_path(Value::validated_to_string(ctx, arguments[0]));
if (argc == 2) {
auto encryption_key = Value::validated_to_binary(ctx, arguments[1], "encryptionKey");
config.path = normalize_realm_path(Value::validated_to_string(ctx, args[0]));
if (args.count == 2) {
auto encryption_key = Value::validated_to_binary(ctx, args[1], "encryptionKey");
config.encryption_key.assign(encryption_key.data(), encryption_key.data() + encryption_key.size());
}
@ -481,18 +524,59 @@ void RealmClass<T>::schema_version(ContextType ctx, FunctionType, ObjectType thi
template<typename T>
void RealmClass<T>::clear_test_state(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void RealmClass<T>::clear_test_state(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
js::clear_test_state();
}
template<typename T>
void RealmClass<T>::copy_bundled_realm_files(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void RealmClass<T>::copy_bundled_realm_files(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
realm::copy_bundled_realm_files();
}
template<typename T>
void RealmClass<T>::delete_file(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
ValueType value = args[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>::delete_model(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
ValueType value = args[0];
SharedRealm& realm = *get_internal<T, RealmClass<T>>(this_object);
std::string model_name = Value::validated_to_string(ctx, value, "deleteModel");
ObjectStore::delete_data_for_object(realm->read_group(), model_name);
}
template<typename T>
void RealmClass<T>::get_default_path(ContextType ctx, ObjectType object, ReturnValue &return_value) {
return_value.set(realm::js::default_path());
@ -528,11 +612,21 @@ void RealmClass<T>::get_schema(ContextType ctx, ObjectType object, ReturnValue &
return_value.set(Schema<T>::object_for_schema(ctx, schema));
}
template<typename T>
void RealmClass<T>::get_in_memory(ContextType ctx, ObjectType object, ReturnValue &return_value) {
return_value.set(get_internal<T, RealmClass<T>>(object)->get()->config().in_memory);
}
template<typename T>
void RealmClass<T>::get_read_only(ContextType ctx, ObjectType object, ReturnValue &return_value) {
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) {
@ -547,12 +641,17 @@ void RealmClass<T>::get_sync_session(ContextType ctx, ObjectType object, ReturnV
#endif
template<typename T>
void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 2);
auto callback_function = Value::validated_to_function(ctx, arguments[1]);
void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(3);
auto callback_function = Value::validated_to_function(ctx, args[1 + (args.count == 3)]);
ValueType session_callback = Value::from_null(ctx);
if (args.count == 3) {
session_callback = Value::validated_to_function(ctx, args[1]);
}
#if REALM_ENABLE_SYNC
auto config_object = Value::validated_to_object(ctx, arguments[0]);
auto config_object = Value::validated_to_object(ctx, args[0]);
ValueType sync_config_value = Object::get_property(ctx, config_object, "sync");
if (!Value::is_undefined(ctx, sync_config_value)) {
realm::Realm::Config config;
@ -563,14 +662,14 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
auto encryption_key = Value::validated_to_binary(ctx, encryption_key_value, "encryptionKey");
config.encryption_key.assign(encryption_key.data(), encryption_key.data() + encryption_key.size());
}
Protected<ObjectType> thiz(ctx, this_object);
SyncClass<T>::populate_sync_config(ctx, thiz, config_object, config);
Protected<FunctionType> protected_callback(ctx, callback_function);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
EventLoopDispatcher<WaitHandler> wait_handler([=](std::error_code error_code) {
HANDLESCOPE
if (!error_code) {
@ -590,7 +689,7 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
});
std::function<WaitHandler> waitFunc = std::move(wait_handler);
std::function<ProgressHandler> progressFunc;
std::function<ProgressHandler> progressFunc;
auto realm = realm::Realm::get_shared_realm(config);
if (auto sync_config = config.sync_config)
@ -623,10 +722,18 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
std::shared_ptr<SyncUser> user = sync_config->user;
if (user && user->state() != SyncUser::State::Error) {
if (auto session = user->session_for_on_disk_path(config.path)) {
if (!Value::is_null(ctx, session_callback)) {
FunctionType session_callback_func = Value::to_function(ctx, session_callback);
auto syncSession = create_object<T, SessionClass<T>>(ctx, new WeakSession(session));
ValueType callback_arguments[1];
callback_arguments[0] = syncSession;
Function<T>::callback(protected_ctx, session_callback_func, protected_this, 1, callback_arguments);
}
if (progressFuncDefined) {
session->register_progress_notifier(std::move(progressFunc), SyncSession::NotifierType::download, false);
}
}
session->wait_for_download_completion([=](std::error_code error_code) {
realm->close(); //capture and keep realm instance for until here
waitFunc(error_code);
@ -651,25 +758,25 @@ void RealmClass<T>::wait_for_download_completion(ContextType ctx, FunctionType,
}
template<typename T>
void RealmClass<T>::objects(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
void RealmClass<T>::objects(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
std::string object_type;
validated_object_schema_for_value(ctx, realm, arguments[0], object_type);
validated_object_schema_for_value(ctx, realm, args[0], object_type);
return_value.set(ResultsClass<T>::create_instance(ctx, realm, object_type));
}
template<typename T>
void RealmClass<T>::object_for_primary_key(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 2);
void RealmClass<T>::object_for_primary_key(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
std::string object_type;
auto &object_schema = validated_object_schema_for_value(ctx, realm, arguments[0], object_type);
auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0], object_type);
NativeAccessor accessor(ctx, realm, object_schema);
auto realm_object = realm::Object::get_for_primary_key(accessor, realm, object_schema, arguments[1]);
auto realm_object = realm::Object::get_for_primary_key(accessor, realm, object_schema, args[1]);
if (realm_object.is_valid()) {
return_value.set(RealmObjectClass<T>::create_instance(ctx, std::move(realm_object)));
@ -680,21 +787,22 @@ void RealmClass<T>::object_for_primary_key(ContextType ctx, FunctionType, Object
}
template<typename T>
void RealmClass<T>::create(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 2, 3);
void RealmClass<T>::create(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(3);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
realm->verify_open();
std::string object_type;
auto &object_schema = validated_object_schema_for_value(ctx, realm, arguments[0], object_type);
auto &object_schema = validated_object_schema_for_value(ctx, realm, args[0], object_type);
ObjectType object = Value::validated_to_object(ctx, arguments[1], "properties");
if (Value::is_array(ctx, arguments[1])) {
ObjectType object = Value::validated_to_object(ctx, args[1], "properties");
if (Value::is_array(ctx, args[1])) {
object = Schema<T>::dict_for_property_array(ctx, object_schema, object);
}
bool update = false;
if (argc == 3) {
update = Value::validated_to_boolean(ctx, arguments[2], "update");
if (args.count == 3) {
update = Value::validated_to_boolean(ctx, args[2], "update");
}
NativeAccessor accessor(ctx, realm, object_schema);
@ -703,15 +811,16 @@ void RealmClass<T>::create(ContextType ctx, FunctionType, ObjectType this_object
}
template<typename T>
void RealmClass<T>::delete_one(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
void RealmClass<T>::delete_one(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
realm->verify_open();
if (!realm->is_in_transaction()) {
throw std::runtime_error("Can only delete objects within a transaction.");
}
ObjectType arg = Value::validated_to_object(ctx, arguments[0]);
ObjectType arg = Value::validated_to_object(ctx, args[0], "object");
if (Object::template is_instance<RealmObjectClass<T>>(ctx, arg)) {
auto object = get_internal<T, RealmObjectClass<T>>(arg);
@ -750,10 +859,11 @@ void RealmClass<T>::delete_one(ContextType ctx, FunctionType, ObjectType this_ob
}
template<typename T>
void RealmClass<T>::delete_all(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void RealmClass<T>::delete_all(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
realm->verify_open();
if (!realm->is_in_transaction()) {
throw std::runtime_error("Can only delete objects within a transaction.");
@ -765,18 +875,18 @@ void RealmClass<T>::delete_all(ContextType ctx, FunctionType, ObjectType this_ob
}
template<typename T>
void RealmClass<T>::write(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
void RealmClass<T>::write(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
FunctionType callback = Value::validated_to_function(ctx, arguments[0]);
FunctionType callback = Value::validated_to_function(ctx, args[0]);
realm->begin_transaction();
try {
Function<T>::call(ctx, callback, this_object, 0, nullptr);
}
catch (std::exception &e) {
catch (...) {
realm->cancel_transaction();
throw;
}
@ -785,54 +895,84 @@ void RealmClass<T>::write(ContextType ctx, FunctionType, ObjectType this_object,
}
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);
validated_notification_name(ctx, arguments[0]);
auto callback = Value::validated_to_function(ctx, arguments[1]);
void RealmClass<T>::begin_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
if (realm->is_closed()) {
throw ClosedRealmException();
}
realm->begin_transaction();
}
template<typename T>
void RealmClass<T>::commit_transaction(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(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, Arguments args, ReturnValue &return_value) {
args.validate_maximum(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, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
validated_notification_name(ctx, args[0]);
auto callback = Value::validated_to_function(ctx, args[1]);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
realm->verify_open();
get_delegate<T>(realm.get())->add_notification(callback);
}
template<typename T>
void RealmClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 2);
void RealmClass<T>::remove_listener(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(2);
validated_notification_name(ctx, arguments[0]);
auto callback = Value::validated_to_function(ctx, arguments[1]);
validated_notification_name(ctx, args[0]);
auto callback = Value::validated_to_function(ctx, args[1]);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
if (realm->is_closed()) {
throw ClosedRealmException();
}
realm->verify_open();
get_delegate<T>(realm.get())->remove_notification(callback);
}
template<typename T>
void RealmClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0, 1);
if (argc) {
validated_notification_name(ctx, arguments[0]);
void RealmClass<T>::remove_all_listeners(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(1);
if (args.count) {
validated_notification_name(ctx, args[0]);
}
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
if (realm->is_closed()) {
throw ClosedRealmException();
}
realm->verify_open();
get_delegate<T>(realm.get())->remove_all_notifications();
}
template<typename T>
void RealmClass<T>::close(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 0);
void RealmClass<T>::close(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(0);
SharedRealm realm = *get_internal<T, RealmClass<T>>(this_object);
realm->close();
}
template<typename T>
void RealmClass<T>::compact(ContextType ctx, FunctionType, ObjectType this_object, Arguments args, ReturnValue &return_value) {
args.validate_maximum(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

@ -49,7 +49,7 @@ struct RealmObjectClass : ClassDefinition<T, realm::Object> {
static void get_property(ContextType, ObjectType, const String &, ReturnValue &);
static bool set_property(ContextType, ObjectType, const String &, ValueType);
static std::vector<String> get_property_names(ContextType, ObjectType);
static void is_valid(ContextType, FunctionType, ObjectType, size_t, const ValueType [], ReturnValue &);
static void get_object_schema(ContextType, FunctionType, ObjectType, size_t, const ValueType [], ReturnValue &);
static void linking_objects(ContextType, FunctionType, ObjectType, size_t, const ValueType [], ReturnValue &);
@ -73,13 +73,13 @@ template<typename T>
void RealmObjectClass<T>::is_valid(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
return_value.set(get_internal<T, RealmObjectClass<T>>(this_object)->is_valid());
}
template<typename T>
void RealmObjectClass<T>::get_object_schema(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
auto object = get_internal<T, RealmObjectClass<T>>(this_object);
return_value.set(Schema<T>::object_for_object_schema(ctx, object->get_object_schema()));
}
template<typename T>
typename T::Object RealmObjectClass<T>::create_instance(ContextType ctx, realm::Object realm_object) {
static String prototype_string = "prototype";
@ -100,7 +100,7 @@ typename T::Object RealmObjectClass<T>::create_instance(ContextType ctx, realm::
if (result != object && !Value::is_null(ctx, result) && !Value::is_undefined(ctx, result)) {
throw std::runtime_error("Realm object constructor must not return another value");
}
return object;
}
@ -127,12 +127,13 @@ bool RealmObjectClass<T>::set_property(ContextType ctx, ObjectType object, const
return false;
}
NativeAccessor<T> accessor(ctx, realm_object->realm(), realm_object->get_object_schema());
if (!Value::is_valid_for_property(ctx, value, *prop)) {
throw TypeErrorException(util::format("%1.%2", realm_object->get_object_schema().name, property_name),
js_type_name_for_property_type(prop->type));
throw TypeErrorException(realm_object->get_object_schema().name, property_name,
js_type_name_for_property_type(prop->type),
accessor.print(value));
}
NativeAccessor<T> accessor(ctx, realm_object->realm(), realm_object->get_object_schema());
realm_object->set_property_value(accessor, property_name, value, true);
return true;
}
@ -162,29 +163,29 @@ std::vector<String<T>> RealmObjectClass<T>::get_property_names(ContextType ctx,
template<typename T>
void realm::js::RealmObjectClass<T>::linking_objects(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 2);
std::string object_type = Value::validated_to_string(ctx, arguments[0], "objectType");
std::string property_name = Value::validated_to_string(ctx, arguments[1], "property");
auto object = get_internal<T, RealmObjectClass<T>>(this_object);
auto target_object_schema = object->realm()->schema().find(object_type);
if (target_object_schema == object->realm()->schema().end()) {
throw std::logic_error(util::format("Could not find schema for type '%1'", object_type));
}
auto link_property = target_object_schema->property_for_name(property_name);
if (!link_property) {
throw std::logic_error(util::format("Type '%1' does not contain property '%2'", object_type, property_name));
}
if (link_property->object_type != object->get_object_schema().name) {
throw std::logic_error(util::format("'%1.%2' is not a relationship to '%3'", object_type, property_name, object->get_object_schema().name));
}
realm::TableRef table = ObjectStore::table_for_object_type(object->realm()->read_group(), target_object_schema->name);
auto row = object->row();
auto tv = row.get_table()->get_backlink_view(row.get_index(), table.get(), link_property->table_column);
return_value.set(ResultsClass<T>::create_instance(ctx, realm::Results(object->realm(), std::move(tv))));
}

View File

@ -179,9 +179,9 @@ ObjectSchema Schema<T>::parse_object_schema(ContextType ctx, ObjectType object_s
ObjectDefaults object_defaults;
ObjectSchema object_schema;
object_schema.name = Object::validated_get_string(ctx, object_schema_object, name_string);
ObjectType properties_object = Object::validated_get_object(ctx, object_schema_object, properties_string, "ObjectSchema must have a 'properties' object.");
object_schema.name = Object::validated_get_string(ctx, object_schema_object, name_string, "ObjectSchema");
ObjectType properties_object = Object::validated_get_object(ctx, object_schema_object, properties_string, "ObjectSchema");
if (Value::is_array(ctx, properties_object)) {
uint32_t length = Object::validated_get_length(ctx, properties_object);
for (uint32_t i = 0; i < length; i++) {
@ -232,7 +232,8 @@ ObjectSchema Schema<T>::parse_object_schema(ContextType ctx, ObjectType object_s
}
template<typename T>
realm::Schema Schema<T>::parse_schema(ContextType ctx, ObjectType schema_object, ObjectDefaultsMap &defaults, ConstructorMap &constructors) {
realm::Schema Schema<T>::parse_schema(ContextType ctx, ObjectType schema_object,
ObjectDefaultsMap &defaults, ConstructorMap &constructors) {
std::vector<ObjectSchema> schema;
uint32_t length = Object::validated_get_length(ctx, schema_object);

View File

@ -160,6 +160,7 @@ class SessionClass : public ClassDefinition<T, WeakSession> {
public:
std::string const name = "Session";
using ProgressHandler = void(uint64_t transferred_bytes, uint64_t transferrable_bytes);
static FunctionType create_constructor(ContextType);
@ -170,6 +171,8 @@ public:
static void simulate_error(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void refresh_access_token(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
static void add_progress_notification(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &);
static void remove_progress_notification(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &);
PropertyMap<T> const properties = {
{"config", {wrap<get_config>, nullptr}},
@ -180,7 +183,9 @@ public:
MethodMap<T> const methods = {
{"_simulateError", wrap<simulate_error>},
{"_refreshAccessToken", wrap<refresh_access_token>}
{"_refreshAccessToken", wrap<refresh_access_token>},
{"addProgressNotification", wrap<add_progress_notification>},
{"removeProgressNotification", wrap<remove_progress_notification>},
};
};
@ -308,7 +313,83 @@ void SessionClass<T>::refresh_access_token(ContextType ctx, FunctionType, Object
}
template<typename T>
class SyncClass : public ClassDefinition<T, void *> {
void SessionClass<T>::add_progress_notification(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 3);
if (auto session = get_internal<T, SessionClass<T>>(this_object)->lock()) {
std::string direction = Value::validated_to_string(ctx, arguments[0], "direction");
std::string mode = Value::validated_to_string(ctx, arguments[1], "mode");
SyncSession::NotifierType notifierType;
if (direction == "download") {
notifierType = SyncSession::NotifierType::download;
}
else if (direction == "upload") {
notifierType = SyncSession::NotifierType::upload;
}
else {
throw std::invalid_argument("Invalid argument 'direction'. Only 'download' and 'upload' progress notification directions are supported");
}
bool is_streaming = false;
if (mode == "reportIndefinitely") {
is_streaming = true;
}
else if (mode == "forCurrentlyOutstandingWork") {
is_streaming = false;
}
else {
throw std::invalid_argument("Invalid argument 'mode'. Only 'reportIndefinitely' and 'forCurrentlyOutstandingWork' progress notification modes are supported");
}
auto callback_function = Value::validated_to_function(ctx, arguments[2], "callback");
Protected<FunctionType> protected_callback(ctx, callback_function);
Protected<ObjectType> protected_this(ctx, this_object);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
std::function<ProgressHandler> progressFunc;
EventLoopDispatcher<ProgressHandler> progress_handler([=](uint64_t transferred_bytes, uint64_t transferrable_bytes) {
HANDLESCOPE
ValueType callback_arguments[2];
callback_arguments[0] = Value::from_number(protected_ctx, transferred_bytes);
callback_arguments[1] = Value::from_number(protected_ctx, transferrable_bytes);
Function<T>::callback(protected_ctx, protected_callback, typename T::Object(), 2, callback_arguments);
});
progressFunc = std::move(progress_handler);
auto registrationToken = session->register_progress_notifier(std::move(progressFunc), notifierType, false);
auto syncSession = create_object<T, SessionClass<T>>(ctx, new WeakSession(session));
PropertyAttributes attributes = ReadOnly | DontEnum | DontDelete;
Object::set_property(ctx, callback_function, "_syncSession", syncSession, attributes);
Object::set_property(ctx, callback_function, "_registrationToken", Value::from_number(protected_ctx, registrationToken), attributes);
}
}
template<typename T>
void SessionClass<T>::remove_progress_notification(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
validate_argument_count(argc, 1);
auto callback_function = Value::validated_to_function(ctx, arguments[0], "callback");
auto syncSessionProp = Object::get_property(ctx, callback_function, "_syncSession");
if (Value::is_undefined(ctx, syncSessionProp) || Value::is_null(ctx, syncSessionProp)) {
return;
}
auto syncSession = Value::validated_to_object(ctx, syncSessionProp);
auto registrationToken = Object::get_property(ctx, callback_function, "_registrationToken");
if (auto session = get_internal<T, SessionClass<T>>(syncSession)->lock()) {
auto reg = Value::validated_to_number(ctx, registrationToken);
session->unregister_progress_notifier(reg);
}
}
template<typename T>
class SyncClass : public ClassDefinition<T, void*> {
using GlobalContextType = typename T::GlobalContext;
using ContextType = typename T::Context;
using FunctionType = typename T::Function;
@ -328,6 +409,7 @@ public:
static void set_sync_log_level(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &);
// private
static std::function<SyncBindSessionHandler> session_bind_callback(ContextType ctx, ObjectType sync_constructor);
static void populate_sync_config(ContextType, ObjectType realm_constructor, ObjectType config_object, Realm::Config&);
// static properties
@ -367,6 +449,24 @@ void SyncClass<T>::set_sync_log_level(ContextType ctx, FunctionType, ObjectType
realm::SyncManager::shared().set_log_level(log_level_2);
}
template<typename T>
std::function<SyncBindSessionHandler> SyncClass<T>::session_bind_callback(ContextType ctx, ObjectType sync_constructor)
{
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
Protected<ObjectType> protected_sync_constructor(ctx, sync_constructor);
return EventLoopDispatcher<SyncBindSessionHandler>([protected_ctx, protected_sync_constructor](const std::string& path, const realm::SyncConfig& config, std::shared_ptr<SyncSession>) {
HANDLESCOPE
ObjectType user_constructor = Object::validated_get_object(protected_ctx, protected_sync_constructor, "User");
FunctionType refreshAccessToken = Object::validated_get_function(protected_ctx, user_constructor, "_refreshAccessToken");
ValueType arguments[3];
arguments[0] = create_object<T, UserClass<T>>(protected_ctx, new SharedUser(config.user));
arguments[1] = Value::from_string(protected_ctx, path);
arguments[2] = Value::from_string(protected_ctx, config.realm_url);
Function::call(protected_ctx, refreshAccessToken, 3, arguments);
});
}
template<typename T>
void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constructor, ObjectType config_object, Realm::Config& config)
{
@ -376,21 +476,8 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
} else if (!Value::is_undefined(ctx, sync_config_value)) {
auto sync_config_object = Value::validated_to_object(ctx, sync_config_value);
ObjectType sync_constructor = Object::validated_get_object(ctx, realm_constructor, std::string("Sync"));
Protected<ObjectType> protected_sync(ctx, sync_constructor);
Protected<typename T::GlobalContext> protected_ctx(Context<T>::get_global_context(ctx));
EventLoopDispatcher<SyncBindSessionHandler> bind([protected_ctx, protected_sync](const std::string& path, const realm::SyncConfig& config, std::shared_ptr<SyncSession>) {
HANDLESCOPE
ObjectType user_constructor = Object::validated_get_object(protected_ctx, protected_sync, std::string("User"));
FunctionType refreshAccessToken = Object::validated_get_function(protected_ctx, user_constructor, std::string("_refreshAccessToken"));
ValueType arguments[3];
arguments[0] = create_object<T, UserClass<T>>(protected_ctx, new SharedUser(config.user));
arguments[1] = Value::from_string(protected_ctx, path.c_str());
arguments[2] = Value::from_string(protected_ctx, config.realm_url.c_str());
Function::call(protected_ctx, refreshAccessToken, 3, arguments);
});
ObjectType sync_constructor = Object::validated_get_object(ctx, realm_constructor, "Sync");
auto bind = session_bind_callback(ctx, sync_constructor);
std::function<SyncSessionErrorHandler> error_handler;
ValueType error_func = Object::get_property(ctx, sync_config_object, "error");
@ -440,6 +527,5 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
}
}
}
} // js
} // realm

View File

@ -20,12 +20,14 @@
#include "execution_context_id.hpp"
#include "property.hpp"
#include "util/format.hpp"
#include <stdexcept>
#include <string>
#include <vector>
#include <realm/binary_data.hpp>
#include <realm/string_data.hpp>
#include <realm/util/to_string.hpp>
#if defined(__GNUC__) && !(defined(DEBUG) && DEBUG)
@ -80,18 +82,16 @@ struct Context {
class TypeErrorException : public std::invalid_argument {
public:
std::string const& prefix() const { return m_prefix; }
std::string const& type() const { return m_type; }
TypeErrorException(StringData object_type, StringData property,
std::string const& type, std::string const& value)
: std::invalid_argument(util::format("%1.%2 must be of type '%3', got (%4)",
object_type, property, type, value))
{}
TypeErrorException(std::string prefix, std::string type) :
std::invalid_argument(prefix + " must be of type: " + type),
m_prefix(std::move(prefix)),
m_type(std::move(type))
{}
private:
std::string m_prefix;
std::string m_type;
TypeErrorException(const char *name, std::string const& type, std::string const& value)
: std::invalid_argument(util::format("%1 must be of type '%2', got (%3)",
name ? name : "JS value", type, value))
{}
};
template<typename T>
@ -138,8 +138,7 @@ struct Value {
#define VALIDATED(return_t, type) \
static return_t validated_to_##type(ContextType ctx, const ValueType &value, const char *name = nullptr) { \
if (!is_##type(ctx, value)) { \
std::string prefix = name ? std::string("'") + name + "'" : "JS value"; \
throw TypeErrorException(prefix, #type); \
throw TypeErrorException(name, #type, to_string(ctx, value)); \
} \
return to_##type(ctx, value); \
}
@ -225,7 +224,7 @@ struct Object {
return Value<T>::validated_to_##type(ctx, get_property(ctx, object, key), std::string(key).c_str()); \
} \
catch (std::invalid_argument &e) { \
throw message ? std::invalid_argument(message) : e; \
throw message ? std::invalid_argument(util::format("Failed to read %1: %2", message, e.what())) : e; \
} \
} \
static return_t validated_get_##type(ContextType ctx, const ObjectType &object, uint32_t index, const char *message = nullptr) { \
@ -233,7 +232,7 @@ struct Object {
return Value<T>::validated_to_##type(ctx, get_property(ctx, object, index)); \
} \
catch (std::invalid_argument &e) { \
throw message ? std::invalid_argument(message) : e; \
throw message ? std::invalid_argument(util::format("Failed to read %1: %2", message, e.what())) : e; \
} \
}

View File

@ -30,7 +30,9 @@ template<typename T>
using ClassDefinition = js::ClassDefinition<Types, T>;
using ConstructorType = js::ConstructorType<Types>;
using ArgumentsMethodType = js::ArgumentsMethodType<Types>;
using MethodType = js::MethodType<Types>;
using Arguments = js::Arguments<Types>;
using PropertyType = js::PropertyType<Types>;
using IndexPropertyType = js::IndexPropertyType<Types>;
using StringPropertyType = js::StringPropertyType<Types>;
@ -369,6 +371,19 @@ JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object,
}
}
template<jsc::ArgumentsMethodType F>
JSValueRef wrap(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], JSValueRef* exception) {
jsc::ReturnValue return_value(ctx);
try {
F(ctx, function, this_object, jsc::Arguments{ctx, argc, arguments}, return_value);
return return_value;
}
catch (std::exception &e) {
*exception = jsc::Exception::value(ctx, e);
return nullptr;
}
}
template<jsc::PropertyType::GetterType F>
JSValueRef wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef* exception) {
jsc::ReturnValue return_value(ctx);

View File

@ -31,6 +31,8 @@ using ClassDefinition = js::ClassDefinition<Types, T>;
using ConstructorType = js::ConstructorType<Types>;
using MethodType = js::MethodType<Types>;
using ArgumentsMethodType = js::ArgumentsMethodType<Types>;
using Arguments = js::Arguments<Types>;
using PropertyType = js::PropertyType<Types>;
using IndexPropertyType = js::IndexPropertyType<Types>;
using StringPropertyType = js::StringPropertyType<Types>;
@ -308,6 +310,21 @@ void wrap(const v8::FunctionCallbackInfo<v8::Value>& info) {
}
}
template<node::ArgumentsMethodType F>
void wrap(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();
node::ReturnValue return_value(info.GetReturnValue());
auto arguments = node::get_arguments(info);
try {
F(isolate, info.Callee(), info.This(), node::Arguments{isolate, arguments.size(), arguments.data()}, return_value);
}
catch (std::exception &e) {
Nan::ThrowError(node::Exception::value(isolate, e));
}
}
template<node::PropertyType::GetterType F>
void wrap(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
v8::Isolate* isolate = info.GetIsolate();

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

@ -120,6 +120,23 @@ module.exports = {
}
},
assertThrowsContaining: function(func, expectedMessage) {
var caught = false;
try {
func();
}
catch (e) {
caught = true;
if (!e.message.includes(expectedMessage)) {
throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown - instead caught: "${e}"`);
}
}
if (!caught) {
throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown`);
}
},
assertTrue: function(condition, errorMessage) {
if (!condition) {
throw new TestFailureError(errorMessage || `Condition ${condition} expected to be true`);

View File

@ -2,33 +2,49 @@
This script creates 3 new objects into a new realm. These are objects are validated to exists by the download api tests.
*/
'use strict';
console.log("download-api-helper started");
const username = process.argv[2];
const realmName = process.argv[3];
const realmModule = process.argv[4];
var Realm = require(realmModule);
Realm.Sync.User.register('http://localhost:9080', username, 'password', (error, user) => {
function createObjects(user) {
const config = {
sync: { user,
url: `realm://localhost:9080/~/${realmName}`,
error: err => console.log(err)
},
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
var realm = new Realm(config);
realm.write(() => {
for (let i = 1; i <= 3; i++) {
realm.create('Dog', { name: `Lassy ${i}` });
}
});
console.log("Dogs count " + realm.objects('Dog').length);
setTimeout(() => process.exit(0), 3000);
}
Realm.Sync.User.register('http://localhost:9080', username, 'password', (error, registeredUser) => {
if (error) {
console.log(error);
process.exit(-2);
} else {
const config = {
sync: { user, url: `realm://localhost:9080/~/${realmName}`, error: err => console.log(err) },
schema: [{ name: 'Dog', properties: { name: 'string' } }]
};
var realm = new Realm(config);
realm.write(() => {
for (let i = 1; i <= 3; i++) {
realm.create('Dog', { name: `Lassy ${i}` });
const registrationError = JSON.stringify(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" + err + "\n" + registrationError + "\n User.login() error:\n" + loginError);
process.exit(-2);
}
else {
createObjects(loggedUser);
}
});
console.log("Dogs count " + realm.objects('Dog').length);
setTimeout(() => process.exit(0), 3000);
}
else {
createObjects(registeredUser);
}
});
});

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

@ -158,4 +158,170 @@ module.exports = {
}
});
},
testDeleteModelMigration: function() {
const schema = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
}
}];
var realm = new Realm({schema: schema});
realm.write(function() {
realm.create('TestObject', ['stringValue', 1]);
});
realm.close();
realm = new Realm({schema: [], schemaVersion: 1});
TestCase.assertEqual(realm.schema.length, 0); // no models
realm.close(); // this won't delete the model
realm = new Realm({schema: schema, schemaVersion: 2});
TestCase.assertEqual(realm.objects('TestObject').length, 1); // the model objects are still there
realm.close();
// now delete the model explicitly, which should delete the objects too
realm = new Realm({schema: [], schemaVersion: 3, migration: function(oldRealm, newRealm) {
newRealm.deleteModel('TestObject');
}});
TestCase.assertEqual(realm.schema.length, 0); // no models
realm.close();
realm = new Realm({schema: schema, schemaVersion: 4});
TestCase.assertEqual(realm.objects('TestObject').length, 0);
realm.close();
},
testDeleteModelInSchema: function() {
const schema = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
}
}];
var realm = new Realm({schema: schema});
realm.write(function() {
realm.create('TestObject', ['stringValue', 1]);
});
realm.close();
// now delete the model explicitly, but it should remain as it's still in the schema
// only the rows should get deleted
realm = new Realm({schema: schema, schemaVersion: 1, migration: function(oldRealm, newRealm) {
newRealm.deleteModel('TestObject');
}});
TestCase.assertEqual(realm.schema.length, 1); // model should remain
TestCase.assertEqual(realm.objects('TestObject').length, 0); // objects should be gone
realm.close();
realm = new Realm({schema: schema, schemaVersion: 2});
TestCase.assertEqual(realm.objects('TestObject').length, 0);
realm.close();
},
testDeleteModelIgnoreNotExisting: function() {
const schema = [{
name: 'TestObject',
properties: {
prop0: 'string',
prop1: 'int',
}
}];
var realm = new Realm({schema: schema});
realm.write(function() {
realm.create('TestObject', ['stringValue', 1]);
});
realm.close();
// non-existing models should be ignore on delete
realm = new Realm({schema: schema, schemaVersion: 1, migration: function(oldRealm, newRealm) {
newRealm.deleteModel('NonExistingModel');
}});
realm.close();
realm = new Realm({schema: schema, schemaVersion: 2});
TestCase.assertEqual(realm.objects('TestObject').length, 1);
realm.close();
},
testDeleteModelWithRelationship: function() {
const ShipSchema = {
name: 'Ship',
properties: {
ship_name: 'string',
captain: 'Captain'
}
};
const CaptainSchema = {
name: 'Captain',
properties: {
captain_name: 'string',
ships: { type: 'linkingObjects', objectType: 'Ship', property: 'captain' }
}
};
var realm = new Realm({schema: [ShipSchema, CaptainSchema]});
realm.write(function() {
realm.create('Ship', {
ship_name: 'My Ship',
captain: {
captain_name: 'John Doe'
}
});
});
TestCase.assertEqual(realm.objects('Captain').length, 1);
TestCase.assertEqual(realm.objects('Ship').length, 1);
TestCase.assertEqual(realm.objects('Ship')[0].captain.captain_name, "John Doe");
TestCase.assertEqual(realm.objects('Captain')[0].ships[0].ship_name, "My Ship");
realm.close();
realm = new Realm({schema: [ShipSchema, CaptainSchema], schemaVersion: 1, migration: function(oldRealm, newRealm) {
TestCase.assertThrows(function(e) {
// deleting a model which is target of linkingObjects results in an exception
newRealm.deleteModel('Captain');
console.log(e);
}, "Table is target of cross-table link columns");
}});
TestCase.assertEqual(realm.objects('Captain').length, 1);
TestCase.assertEqual(realm.objects('Ship').length, 1);
realm.close();
realm = new Realm({schema: [ShipSchema, CaptainSchema], schemaVersion: 2, migration: function(oldRealm, newRealm) {
// deleting a model which isn't target of linkingObjects works fine
newRealm.deleteModel('Ship');
}});
TestCase.assertEqual(realm.objects('Captain').length, 1);
TestCase.assertEqual(realm.objects('Ship').length, 0);
TestCase.assertEqual(realm.objects('Captain')[0].ships.length, 0);
realm.close();
},
};

View File

@ -0,0 +1,130 @@
////////////////////////////////////////////////////////////////////////////
//
// 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));
}
function repeatUntil(fn, predicate) {
let retries = 0
function check() {
if (retries > 3) {
return Promise.reject(new Error("operation timed out"));
}
++retries;
return fn().then(x => predicate(x) ? x : wait(100).then(check));
}
return check;
}
module.exports = {
testApplyAndGetGrantedPermissions() {
return createUsersWithTestRealms(1)
.then(([user]) => {
return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read')
.then(repeatUntil(() => user.getGrantedPermissions('any'),
permissions => permissions.length > 1))
.then(permissions => {
TestCase.assertEqual(permissions[1].path, `/${user.identity}/test`);
TestCase.assertEqual(permissions[1].mayRead, true);
TestCase.assertEqual(permissions[1].mayWrite, false);
TestCase.assertEqual(permissions[1].mayManage, false);
});
});
},
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 realmUrl;
})
.then(repeatUntil(() => user2.getGrantedPermissions('any'),
permissions => permissions.length > 1))
.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);
}
});
});
});
},
}

File diff suppressed because it is too large Load Diff

View File

@ -383,23 +383,23 @@ module.exports = {
TestCase.assertEqual(snapshot.length, 0);
});
},
testResultsFindIndexOfObject: function() {
var realm = new Realm({schema: [schemas.TestObject]});
var object1, object2, object3;
realm.write(function() {
object1 = realm.create('TestObject', {doubleCol: 1});
object2 = realm.create('TestObject', {doubleCol: 2});
object3 = realm.create('TestObject', {doubleCol: 2});
});
// Search in base table
const objects = realm.objects('TestObject');
TestCase.assertEqual(objects.indexOf(object1), 0);
TestCase.assertEqual(objects.indexOf(object2), 1);
TestCase.assertEqual(objects.indexOf(object3), 2);
// Search in filtered query
const results = objects.filtered("doubleCol == 2");
TestCase.assertEqual(results.indexOf(object1), -1);
@ -408,7 +408,7 @@ module.exports = {
const nonRealmObject = {test: "this is an object"};
TestCase.assertEqual(objects.indexOf(nonRealmObject), -1);
// Searching for object from the wrong realm
var realm2 = new Realm({path: '2.realm', schema: realm.schema});
var object4;
@ -421,29 +421,40 @@ module.exports = {
},
testAddListener: function() {
return new Promise((resolve, _reject) => {
var realm = new Realm({ schema: [schemas.TestObject] });
if (typeof navigator !== 'undefined' && /Chrome/.test(navigator.userAgent)) { // eslint-disable-line no-undef
// FIXME: async callbacks do not work correctly in Chrome debugging mode
return;
}
realm.write(() => {
realm.create('TestObject', { doubleCol: 1 });
realm.create('TestObject', { doubleCol: 2 });
realm.create('TestObject', { doubleCol: 3 });
});
const realm = new Realm({ schema: [schemas.TestObject] });
realm.write(() => {
realm.create('TestObject', { doubleCol: 1 });
realm.create('TestObject', { doubleCol: 2 });
realm.create('TestObject', { doubleCol: 3 });
});
let resolve, first = true;
return new Promise((r, _reject) => {
resolve = r;
realm.objects('TestObject').addListener((testObjects, changes) => {
// TODO: First notification is empty, so perform these
// assertions on the second call. However, there is a race condition
// in React Native, so find a way to do this in a robust way.
//TestCase.assertEqual(testObjects.length, 4);
//TestCase.assertEqual(changes.insertions.length, 1);
if (first) {
TestCase.assertEqual(testObjects.length, 3);
TestCase.assertEqual(changes.insertions.length, 0);
}
else {
TestCase.assertEqual(testObjects.length, 4);
TestCase.assertEqual(changes.insertions.length, 1);
}
first = false;
resolve();
});
realm.write(() => {
realm.create('TestObject', { doubleCol: 1 });
}).then(() => {
return new Promise((r, _reject) => {
realm.write(() => {
realm.create('TestObject', { doubleCol: 1 });
});
resolve = r;
});
})
});
}
};

View File

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

View File

@ -26,7 +26,7 @@ const Realm = require('realm');
const TestCase = require('./asserts');
const isNodeProccess = (typeof process === 'object' && process + '' === '[object process]');
console.log("isnode " + isNodeProccess + " typeof " + (typeof(process) === 'object'));
function node_require(module) {
return require(module);
}
@ -84,13 +84,23 @@ function runOutOfProcess(nodeJsFilePath) {
fs.appendFileSync(tmpFile.fd, content, { encoding: 'utf8' });
nodeArgs[0] = tmpFile.name;
return new Promise((resolve, reject) => {
const child = execFile('node', nodeArgs, { cwd: tmpDir.name }, (error, stdout, stderr) => {
if (error) {
reject(new Error(`Error executing ${nodeJsFilePath} Error: ${error}`));
try {
console.log('runOutOfProcess command\n node ' + nodeArgs.join(" "));
const child = execFile('node', nodeArgs, { cwd: tmpDir.name }, (error, stdout, stderr) => {
if (error) {
console.error("runOutOfProcess failed\n" + error);
reject(new Error(`Running ${nodeJsFilePath} failed. error: ${error}`));
return;
}
console.log('runOutOfProcess success\n' + stdout);
resolve();
});
}
resolve();
});
})
catch (e) {
reject(e);
}
});
}
module.exports = {
@ -150,7 +160,7 @@ module.exports = {
const realmName = uuid();
const expectedObjectsCount = 3;
runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
return runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
.then(() => {
return promisifiedLogin('http://localhost:9080', username, 'password').then(user => {
const accessTokenRefreshed = this;
@ -186,9 +196,9 @@ module.exports = {
const realmName = uuid();
const expectedObjectsCount = 3;
runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
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;
@ -239,9 +249,9 @@ module.exports = {
const realmName = uuid();
const expectedObjectsCount = 3;
runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
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;
@ -275,9 +285,9 @@ module.exports = {
const realmName = uuid();
const expectedObjectsCount = 3;
runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
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 = {
@ -322,9 +332,9 @@ module.exports = {
const realmName = uuid();
const expectedObjectsCount = 3;
runOutOfProcess(__dirname + '/download-api-helper.js', username, realmName, REALM_MODULE_PATH)
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;
@ -429,7 +439,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) => {
@ -449,5 +459,191 @@ module.exports = {
session._simulateError(123, 'simulated error');
});
});
}
},
testProgressNotificationsForRealmConstructor() {
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
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 new Promise((resolve, reject) => {
let config = {
sync: {
user,
url: `realm://localhost:9080/~/${realmName}`
},
schema: [{ name: 'Dog', properties: { name: 'string' } }],
};
let realm = new Realm(config);
const progressCallback = (transferred, total) => {
resolve();
};
realm.syncSession.addProgressNotification('download', 'reportIndefinitely', progressCallback);
setTimeout(function() {
reject("Progress Notifications API failed to call progress callback for Realm constructor");
}, 5000);
});
});
});
},
testProgressNotificationsUnregisterForRealmConstructor() {
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
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 new Promise((resolve, reject) => {
let config = {
sync: {
user,
url: `realm://localhost:9080/~/${realmName}`
},
schema: [{ name: 'Dog', properties: { name: 'string' } }],
};
let realm = new Realm(config);
let unregisterFunc;
let writeDataFunc = () => {
realm.write(() => {
for (let i = 1; i <= 3; i++) {
realm.create('Dog', { name: `Lassy ${i}` });
}
});
}
let syncFinished = false;
let failOnCall = false;
const progressCallback = (transferred, total) => {
if (failOnCall) {
reject(new Error("Progress callback should not be called after removeProgressNotification"));
}
syncFinished = transferred === total;
//unregister and write some new data.
if (syncFinished) {
failOnCall = true;
unregisterFunc();
//use second callback to wait for sync finished
realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', (x, y) => {
if (x === y) {
resolve();
}
});
writeDataFunc();
}
};
realm.syncSession.addProgressNotification('upload', 'reportIndefinitely', progressCallback);
unregisterFunc = () => {
realm.syncSession.removeProgressNotification(progressCallback);
};
writeDataFunc();
});
});
});
},
testProgressNotificationsForRealmOpen2() {
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
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 new Promise((resolve, reject) => {
let config = {
sync: {
user,
url: `realm://localhost:9080/~/${realmName}`
},
schema: [{ name: 'Dog', properties: { name: 'string' } }],
};
let progressCalled = false;
Realm.open(config)
.progress((transferred, total) => {
progressCalled = true;
})
.then(() => {
TestCase.assertTrue(progressCalled);
resolve();
})
.catch((e) => reject(e));
setTimeout(function() {
reject("Progress Notifications API failed to call progress callback for Realm constructor");
}, 5000);
});
});
});
},
testProgressNotificationsForRealmOpenAsync2() {
if (!isNodeProccess) {
return Promise.resolve();
}
const username = uuid();
const realmName = uuid();
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 new Promise((resolve, reject) => {
let config = {
sync: {
user,
url: `realm://localhost:9080/~/${realmName}`
},
schema: [{ name: 'Dog', properties: { name: 'string' } }],
};
let progressCalled = false;
Realm.openAsync(config,
(error, realm) => {
if (error) {
reject(error);
return;
}
TestCase.assertTrue(progressCalled);
resolve();
},
(transferred, total) => {
progressCalled = true;
});
setTimeout(function() {
reject("Progress Notifications API failed to call progress callback for Realm constructor");
}, 5000);
});
});
});
},
}

View File

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

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

View File

@ -168,6 +168,7 @@ extern NSMutableArray *RCTGetModuleClasses(void);
+ (void)waitForCondition:(BOOL *)condition description:(NSString *)description {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:30.0];
RCTBridge *bridge = [self currentBridge];
while (!*condition) {
if ([timeout timeIntervalSinceNow] < 0) {
@ -180,6 +181,7 @@ extern NSMutableArray *RCTGetModuleClasses(void);
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[runLoop runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
[NSThread sleepForTimeInterval:0.01]; // Bad things may happen without some sleep.
[bridge.eventDispatcher sendAppEventWithName:@"realm-dummy" body:nil]; // Ensure RN has an event loop running
}
}
}

View File

@ -20,6 +20,7 @@
/* eslint-disable no-console */
'use strict';
const isNodeProccess = (typeof process === 'object' && process + '' === '[object process]');
const fs = require('fs');
const path = require('path');
@ -28,7 +29,11 @@ const Realm = require('realm');
const RealmTests = require('../js');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
const isDebuggerAttached = typeof v8debug === 'object';
let isDebuggerAttached = typeof v8debug === 'object';
if (!isDebuggerAttached && isNodeProccess) {
isDebuggerAttached = /--debug|--inspect/.test(process.execArgv.join(' '));
}
if (isDebuggerAttached) {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000000;
}