diff --git a/.gitignore b/.gitignore index 40cde46388..398caea88e 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,8 @@ yarn.lock # Android/IJ /android /react-native/android/src/main/jni/core +/react-native/android/.cxx/ +/build-android/ .idea .gradle local.properties @@ -90,7 +92,6 @@ integration-tests/**/.lock /react-native/android/src/main/jni/src/ /build-tmp*/ /cmakebuild/ -/build-realm-android/ /react-native/android/src/main/java/io/realm/react/Version.java /react-native/android/src/main/jniLibs/ /realm*.tgz diff --git a/.gitmodules b/.gitmodules index e4a420eacc..1582de9405 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "vendor/GCDWebServer"] - path = vendor/GCDWebServer - url = https://github.com/swisspol/GCDWebServer.git [submodule "docs/jsdoc-template"] path = docs/jsdoc-template url = https://github.com/realm/realm-jsdoc.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b98e07749..d20b5ad1ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,8 +83,78 @@ * Migrated to `std::optional` and `std::nullopt`. * Disabled testing on iOS and Catalyst on legacy CI system. -## 10.19.5 (2022-7-6) +## 11.0.0-rc.1 (2022-7-11) + +### Notes +This is primarily a re-release of `11.0.0-rc.0`, which is compatible with React Native v0.69.0 or above. +The release is based on Realm JS v10.19.5. + +### Enhancements +* None. + +### Fixed +* None. + +### Compatibility +* React Native equal to or above `v0.69.0` and above, since we're shipping binaries pre-compiled against the JSI ABI. +* Atlas App Services. +* Realm Studio v12.0.0. +* APIs are backwards compatible with all previous releases of Realm JavaScript in the 10.5.x series. +* File format: generates Realms with format v22 (reads and upgrades file format v5 or later for non-synced Realm, upgrades file format v10 or later for synced Realms). + +## 11.0.0-rc.0 (2022-7-7) + +### Notes +Based on Realm JS v10.19.5: See changelog below for details on enhancements and fixes introduced between this and the previous pre release (which was based on Realm JS v10.17.0). + +### Breaking change +* Removed all code related to the legacy Chrome Debugger. Please use [Flipper](https://fbflipper.com/) as debugger. +* Model classes passed as schema to the `Realm` constructor must now extend `Realm.Object` and will no longer have their constructors called when pulling an object of that type from the database. Existing classes already extending `Realm.Object` now need to call the `super` constructor passing two arguments: + - `realm`: The Realm to create the object in. + - `values`: Values to pass to the `realm.create` call when creating the object in the database. +* Renamed the `RealmInsertionModel` type to `Unmanaged` to simplify and highlight its usage. +* Installing via NPM from a `git://` URL is no longer supported, since we removed `src` and `vendor` from the NPM bundle, to reduce size blow-up caused by files recently added to the sub-module. This will force end-users to checkout the Git repository from GitHub when building from source. ([#4060](https://github.com/realm/realm-js/issues/4060)) + +### Enhancements +* Adding support for Hermes on iOS & Android. +* Class-based models (i.e. user defined classes extending `Realm.Object` and passed through the `schema` when opening a Realm), will now create object when their constructor is called: + +```ts +class Person extends Realm.Object { + name!: string; + + static schema = { + name: "Person", + properties: { name: "string" }, + }; +} + +const realm = new Realm({ schema: [Person] }); +realm.write(() => { + const alice = new Person(realm, { name: "Alice" }); + // A Person { name: "Alice" } is now persisted in the database + console.log("Hello " + alice.name); +}); +``` + +### Fixed +* Fixed build error (call to implicitly-deleted copy constructor of 'realm::js::RealmClass::Arguments') (follow up to [#4568](https://github.com/realm/realm-js/pull/4568)) + +### Compatibility +* React Native equal to or above `v0.66.0` and below `v0.69.0` (not included), since we're shipping binaries pre-compiled against the JSI ABI. +* Atlas App Services. +* Realm Studio v12.0.0. +* APIs are backwards compatible with all previous releases of Realm JavaScript in the 10.5.x series. +* File format: generates Realms with format v22 (reads and upgrades file format v5 or later for non-synced Realm, upgrades file format v10 or later for synced Realms). + +### Internal +* Remove the previous implementation to the JavaScriptCore engine (in `src/jsc`). +* Upgrade Example to RN v0.68.2 +* Upgrade dependencies of the Realm Web integration tests +* Throw instances of `Error` instead of plain objects on app errors. +* Make integration tests on React Native Android connect to host machine by default +## 10.19.5 (2022-7-6) ### Enhancements * None. @@ -100,7 +170,7 @@ ### Internal * Using Realm Core v12.3.0. -10.19.4 (2022-7-5) +## 10.19.4 (2022-7-5) ### Enhancements * Allow flexible sync with discard local client resets. ([realm/realm-core#5404](https://github.com/realm/realm-core/pull/5404)) @@ -300,6 +370,11 @@ const Person = { * Fixed a typo in the `testRealmConversions` test which prevented some test scenarios from executing. * Upgraded Realm Core from v11.14.0 to v11.15.0. +## 10.20.0-beta.5 (2022-4-13) + +### Notes +Based on Realm JS v10.16.0: See changelog below for details on enhancements and fixes introduced between this and the previous pre release (which was based on Realm JS v10.15.0). + ## 10.16.0 (2022-4-12) ### Enhancements @@ -317,8 +392,16 @@ const Person = { ### Internal * Upgraded Realm Core from v11.13.0 to v11.14.0. -## 10.15.0 (2022-4-11) +## 10.20.0-beta.4 (2022-4-11) +### Notes +Based on Realm JS v10.15.0: See changelog below for details on enhancements and fixes introduced between this and the previous pre release (which was based on Realm JS v10.14.0). + +### Fixed +* Changed "react-native" main field to point to a `lib/index.native.js` file to help bundlers pick the right file when loading our library on React Native. ([#4459](https://github.com/realm/realm-js/issues/4459)) +* Fixed resolving the "react-native" package when building from source, enabling developers to run the `./scripts/build-ios.sh` script themselves to build our iOS artifacts with the same version of Xcode / LLVM as they're building their app. + +## 10.15.0 (2022-4-11) ### Enhancements * None. @@ -339,6 +422,11 @@ const Person = { * Upgraded Realm Core from v11.12.0 to v11.13.0. * Added a failing test case for Node.js scripts failing to exit cleanly ([#4556](https://github.com/realm/realm-js/pull/4556)) +## 10.20.0-beta.3 (2022-3-24) + +### Notes +Based on Realm JS v10.14.0: See changelog below for details on enhancements and fixes introduced between this and the previous pre release (which was based on Realm JS v10.13.0). + ## 10.14.0 (2022-3-24) ### Enhancements @@ -371,6 +459,11 @@ const Person = { * Fixed React Native Android integration test harness to read only one pid when starting logcat. * Added a script to generate JS template apps from TS, and updated JS templates. ([4374](https://github.com/realm/realm-js/pull/4374)) +## 10.20.0-beta.2 Release notes (2022-2-14) + +### Notes +Based on Realm JS v10.13.0: See changelog below for details on enhancements and fixes introduced between this and the previous pre release (which was based on Realm JS v10.12.0). + ## 10.13.0 (2022-2-11) ### Enhancements @@ -396,6 +489,23 @@ const Person = { * Fixed an issue where some references were not updated from `Subscriptions` to `SubscriptionSet`. ([#4298](https://github.com/realm/realm-js/pull/4298)) * Submitting [analytics](https://github.com/realm/realm-js/blob/master/README.md#analytics) as a postinstall script. +## 10.20.0-beta.1 (2022-1-27) + +### Notes +Based on Realm JS v10.12.0: See changelog below for details on enhancements and fixes introduced between this and the previous pre release (which was based on Realm JS v10.11.0). + +### Breaking change +* Removed all code related to the legacy Chrome Debugger. Please use [Flipper](https://fbflipper.com/) as debugger. + +### Enhancements +* None. + +### Fixed +* Fixed "JSCRuntime destroyed with a dangling API object" assertion when reloading an app in debug mode while running with Hermes engine disabled. ([#4115](https://github.com/realm/realm-js/issues/4115), since 10.20.0-alpha.0) + +### Internal +* Remove the previous implementation to the JavaScriptCore engine (in `src/jsc`). + ## 10.12.0 (2022-1-24) ### Notes @@ -435,8 +545,19 @@ Please note the following API changes from the `10.12.0-beta.1` release of Flexi * Enabled `strictNullChecks` for integration tests * Updated release instructions -## 10.11.0 (2021-12-21) +## 10.20.0-beta.0 (2021-12-21) + +### Notes +Based on Realm JS v10.11.0: See changelog below for details on enhancements and fixes introduced between this and the previous pre release (which was based on Realm JS v10.10.1). +### Enhancements +* Catching missing libjsi.so when loading the librealm.so and rethrowing a more meaningful error, instructing users to upgrade their version of React Native. + +### Fixed +* Fixed support of user defined classes that don't extend `Realm.Object`. +* Fixed throwing "Illegal constructor" when `new` constructing anything other than `Realm` and `Realm.Object`. + +## 10.11.0 (2021-12-21) ### Enhancements * Added templates for Expo (https://www.npmjs.com/package/@realm/expo-template-js and https://www.npmjs.com/package/@realm/expo-template-ts). * A new mode `discardLocal` for client reset has been introduced. The old behavior is supported (but deprecated) through the `manual` mode. The new mode will discard any local changes, and a fresh copy of the Realm will be downloaded. An example of the configuration: @@ -487,6 +608,23 @@ const config = { * Added a performance test suite to the integration test. * Upgraded Realm Core from v11.6.1 to v11.7.0. +## 10.20.0-alpha.2 (2021-11-25) + +### Notes +NOTE: DO NOT USE THIS RELEASE IN PRODUCTION! +NOTE: This is an early (alpha) release with Hermes/JSI support: We expect crashes and bugs. + +Based on Realm JS v10.10.1: See changelog below for details on enhancements and fixes introduced between this and the previous pre release (which was based on Realm JS v10.8.0). + +### Enhancements +* None. + +### Fixed +* Hot reloading on Android no longer crash the app. + +### Internal +* Restructured C++ namespaces and files to reflect that we support JSI, not just Hermes. + ## 10.10.1 (2021-11-18) ### Enhancements @@ -599,8 +737,18 @@ const config = { * Using Realm Core v11.4.1. * Small fix to Jenkins to publish Docker image for Raspberry Pi. -## 10.8.0 (2021-9-14) +## 10.20.0-alpha.1 (2021-9-1) +### Notes +NOTE: DO NOT USE THIS RELEASE IN PRODUCTION! +NOTE: This is an early (alpha) release with Hermes/JSI support. Only iOS is supported and we expect crashes and bugs. + +Based on Realm JS v10.8.0: See changelog below for details on enhancements and fixes introduced between this and the previous pre release (which was based on Realm JS v10.7.0). + +### Enhancements +* Adding support for Hermes on Android. + +## 10.8.0 (2021-9-14) ### Enhancements * Synchronized Realms are no longer opened twice, cutting the address space and file descriptors used in half. ([realm/realm-core#4839](https://github.com/realm/realm-core/pull/4839)) * `Realm.write()` now returns callback return value. ([#2237](https://github.com/realm/realm-js/issues/2237)) @@ -624,6 +772,17 @@ const config = { * Extend Jest test runner to cover opening and closing of a Realm. * Disable analytics if the `CI` environment variable is set to some value. +## 10.20.0-alpha.0 (2021-9-1) + +### Notes +NOTE: DO NOT USE THIS RELEASE IN PRODUCTION! +NOTE: This is an early (alpha) release with Hermes/JSI support. Only iOS is supported and we expect crashes and bugs. + +Based on Realm JS v10.7.0: See changelog below for details on enhancements and fixes introduced by that version. + +### Enhancements +- Adding support for Hermes (iOS only). + ## 10.7.0 (2021-8-30) ### Enhancements diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b56824a85..4d3b7df8fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON) list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") +set(PACKAGE_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + if(DEFINED CMAKE_JS_VERSION) include(NodeJSTargets) endif() diff --git a/Dockerfile b/Dockerfile index 11e84aa105..cda427e5f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,6 +29,6 @@ ENV LD_LIBRARY_PATH /opt/rh/httpd24/root/usr/lib64:/opt/rh/python27/root/usr/lib # Ensure a new enough version of CMake is available. RUN cd /opt \ - && curl -O -J https://cmake.org/files/v3.15/cmake-3.15.2-Linux-x86_64.tar.gz \ - && tar zxf cmake-3.15.2-Linux-x86_64.tar.gz -ENV PATH "/opt/cmake-3.15.2-Linux-x86_64/bin:$PATH" \ No newline at end of file + && curl -O -J https://cmake.org/files/v3.21/cmake-3.21.3-linux-x86_64.tar.gz \ + && tar zxf cmake-3.21.3-linux-x86_64.tar.gz +ENV PATH "/opt/cmake-3.21.3-linux-x86_64/bin/:$PATH" \ No newline at end of file diff --git a/Dockerfile.android b/Dockerfile.android index 27efd67de5..1d95fe439b 100644 --- a/Dockerfile.android +++ b/Dockerfile.android @@ -58,7 +58,7 @@ RUN echo 'Installing Android SDK' && \ "platform-tools" \ "build-tools;${ANDROID_BUILD_TOOLS_VERSION}" \ "platforms;android-26" \ - "cmake;3.10.2.4988404"\ + "cmake;3.18.1"\ "system-images;android-29;default;x86" && \ \ echo 'Installing Android NDK' && \ @@ -75,8 +75,8 @@ RUN mkdir -p $NVM_DIR \ && chmod a+rwX -R $NVM_DIR # Ensure a new enough version of CMake is available. -RUN mkdir -p /home/jenkins/cmake && \ - cd /home/jenkins/cmake && \ - curl -O -J https://cmake.org/files/v3.17/cmake-3.17.5-Linux-x86_64.tar.gz &&\ - tar zxf cmake-3.17.5-Linux-x86_64.tar.gz -ENV PATH "/home/jenkins/cmake/cmake-3.17.5-Linux-x86_64/bin:$PATH" \ No newline at end of file +RUN mkdir -p /home/jenkins/cmake \ + && cd /home/jenkins/cmake \ + && curl -O -J https://cmake.org/files/v3.21/cmake-3.21.3-linux-x86_64.tar.gz \ + && tar zxf cmake-3.21.3-linux-x86_64.tar.gz +ENV PATH "/home/jenkins/cmake/cmake-3.21.3-linux-x86_64/bin/:$PATH" diff --git a/README.md b/README.md index 4a2da07283..ef8bd99390 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,8 @@ See [CONTRIBUTING.md](https://github.com/realm/realm-js/blob/master/CONTRIBUTING ## Known issues -* Realm is not compatible with the Chrome Debugger. The following debugging methods are supported. - * [Flipper](https://fbflipper.com/) has many similar features in relation to the Chrome Debugger. Please consider trying out our [Hermes release](https://github.com/realm/realm-js/issues/3940) to use the Hermes Debugger in Flipper and set breakpoints in your code. +* Realm is not compatible with the legacy Chrome Debugger. The following debugging methods are supported: + * [Flipper](https://fbflipper.com/) has many similar features in relation to the Chrome Debugger. * [Safari](https://reactnative.dev/docs/debugging#safari-developer-tools) also has a similar feature set, but requires [some setup](https://blog.nparashuram.com/2019/10/debugging-react-native-ios-apps-with.html) and only supports debugging in iOS. * **NOTE:** For the above methods, it is not neccessary to enable `Debug with Chrome` in the Debug Menu. * [What to expect in Realm JavaScript v11.0.0](https://github.com/realm/realm-js/discussions/4839) diff --git a/Realm.xcworkspace/xcshareddata/Realm.xcscmblueprint b/Realm.xcworkspace/xcshareddata/Realm.xcscmblueprint index ab8ab04a1e..30f010e748 100644 --- a/Realm.xcworkspace/xcshareddata/Realm.xcscmblueprint +++ b/Realm.xcworkspace/xcshareddata/Realm.xcscmblueprint @@ -11,7 +11,6 @@ "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "5EE721F9-041C-4877-9E73-A925C9DB080A", "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { "40F53A12E4AE40C654358321B91166ABD3E910A6" : "realm-js\/", - "F6F96CA34C5878B0A9123C7C37855491A5E599DA" : "realm-js\/vendor\/GCDWebServer\/", "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" : "realm-js\/src\/object-store\/" }, "DVTSourceControlWorkspaceBlueprintNameKey" : "Realm", @@ -27,11 +26,6 @@ "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/realm\/realm-object-store.git", "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8F3C415DA79CDA7D23734F285B95F9F9A3C0CB81" - }, - { - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/swisspol\/GCDWebServer.git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", - "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "F6F96CA34C5878B0A9123C7C37855491A5E599DA" } ] } \ No newline at end of file diff --git a/RealmJS.podspec b/RealmJS.podspec index d37d33cf9c..815ada39a0 100644 --- a/RealmJS.podspec +++ b/RealmJS.podspec @@ -42,8 +42,9 @@ Pod::Spec.new do |s| s.source = { :http => 'https://github.com/realm/realm-js/blob/master/CONTRIBUTING.md#how-to-debug-react-native-podspec' } s.source_files = 'react-native/ios/RealmReact/*.mm' + s.public_header_files = 'react-native/ios/RealmReact/*.h' - s.frameworks = uses_frameworks ? ['JavaScriptCore', 'React'] : ['JavaScriptCore'] + s.frameworks = uses_frameworks ? ['React'] : [] s.library = 'c++', 'z', 'compression' @@ -66,6 +67,4 @@ Pod::Spec.new do |s| s.vendored_frameworks = 'react-native/ios/realm-js-ios.xcframework' s.dependency 'React' - # TODO: Ensure the same version of GCDWebServer is used for Android - s.dependency 'GCDWebServer' end diff --git a/dependencies.list b/dependencies.list index 6fc4f673fd..5f490df2c6 100644 --- a/dependencies.list +++ b/dependencies.list @@ -1,5 +1,5 @@ PACKAGE_NAME=realm-js -VERSION=10.21.0 +VERSION=11.0.0-rc.1 REALM_CORE_VERSION=12.6.0 NAPI_VERSION=5 OPENSSL_VERSION=1.1.1g diff --git a/integration-tests/README.md b/integration-tests/README.md index bd30a251b4..874e67a80e 100644 --- a/integration-tests/README.md +++ b/integration-tests/README.md @@ -99,6 +99,11 @@ In order to debug the tests with `lldb` attached to debug C++, you can use the V This bypasses the usual startup script (as this spawns a child process for the tests, which stops `lldb` working), so you need to have the app importer running separately when doing this: `npm run app-importer` from the `integration-tests/tests` directory. +### Environment variables + +// TODO: Provide an explanation of the environment variables. +// In particular `MOCHA_REMOTE_CONTEXT=missingServer` to run tests without a test server. + --- ## Maintaining the tests @@ -120,7 +125,7 @@ Tests will have access to the following globals: - [the Mocha hook globals](https://mochajs.org/#hooks) (describe, it, after, before, etc.). - `fs` the lowest common denominator of the [`fs-extra`](https://www.npmjs.com/package/fs-extra) and [`react-native-fs`](https://www.npmjs.com/package/react-native-fs) APIs. - `path` the lowest common denominator of the Node.js path interface and a [node-independent implementation of Node's path](https://www.npmjs.com/package/path-browserify) module. -- `it.skipIf` and `describe.skipIf` skips tests based on the environment, see `tests/src/utils/skip-if.js` for a detailed explanation. +- `it.skipIf` and `describe.skipIf` skips tests based on the environment, see `tests/src/utils/skip-if.ts` for a detailed explanation. Remember to close or clean up Realms accessed during tests. The `Realm.clearTestState` can be called after each test, which closes and removes all Realm files in the default directory. diff --git a/integration-tests/environments/react-native/Gemfile.lock b/integration-tests/environments/react-native/Gemfile.lock new file mode 100644 index 0000000000..71c42ca8eb --- /dev/null +++ b/integration-tests/environments/react-native/Gemfile.lock @@ -0,0 +1,100 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.5) + rexml + activesupport (6.1.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.8.0) + public_suffix (>= 2.0.2, < 5.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + claide (1.1.0) + cocoapods (1.11.3) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.11.3) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 1.4.0, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.4.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 1.0, < 3.0) + xcodeproj (>= 1.21.0, < 2.0) + cocoapods-core (1.11.3) + activesupport (>= 5.0, < 7) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (1.6.3) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.1.10) + escape (0.0.4) + ethon (0.15.0) + ffi (>= 1.15.0) + ffi (1.15.5) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.8.3) + i18n (1.11.0) + concurrent-ruby (~> 1.0) + json (2.6.2) + minitest (5.16.2) + molinillo (0.8.0) + nanaimo (0.3.0) + nap (1.1.0) + netrc (0.11.0) + public_suffix (4.0.7) + rexml (3.2.5) + ruby-macho (2.5.1) + typhoeus (1.4.0) + ethon (>= 0.9.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + xcodeproj (1.22.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (~> 3.2.4) + zeitwerk (2.6.0) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods (~> 1.11, >= 1.11.2) + +RUBY VERSION + ruby 2.7.5p203 + +BUNDLED WITH + 2.3.17 diff --git a/integration-tests/environments/react-native/android/app/build.gradle b/integration-tests/environments/react-native/android/app/build.gradle index 2e00fb858c..cbfa868add 100644 --- a/integration-tests/environments/react-native/android/app/build.gradle +++ b/integration-tests/environments/react-native/android/app/build.gradle @@ -79,7 +79,7 @@ import org.apache.tools.ant.taskdefs.condition.Os */ project.ext.react = [ - enableHermes: false, // clean and rebuild if changing + enableHermes: System.getenv().getOrDefault("USE_HERMES", "1") == "1", // default: true ] apply from: "../../node_modules/react-native/react.gradle" diff --git a/integration-tests/environments/react-native/ios/RealmReactNativeTests.xcodeproj/project.pbxproj b/integration-tests/environments/react-native/ios/RealmReactNativeTests.xcodeproj/project.pbxproj index a19a8d63d6..b47e365b7f 100644 --- a/integration-tests/environments/react-native/ios/RealmReactNativeTests.xcodeproj/project.pbxproj +++ b/integration-tests/environments/react-native/ios/RealmReactNativeTests.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 717F9684289D0B55007E7983 /* Compatibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Compatibility.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = RealmReactNativeTests/LaunchScreen.storyboard; sourceTree = ""; }; 89C6BE57DB24E9ADA2F236DE /* Pods-RealmReactNativeTests-RealmReactNativeTestsTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RealmReactNativeTests-RealmReactNativeTestsTests.release.xcconfig"; path = "Target Support Files/Pods-RealmReactNativeTests-RealmReactNativeTestsTests/Pods-RealmReactNativeTests-RealmReactNativeTestsTests.release.xcconfig"; sourceTree = ""; }; + DC09D54926CCEF1000A3B18B /* src */ = {isa = PBXFileReference; lastKnownFileType = folder; name = src; path = ../../../../src; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ @@ -123,6 +124,7 @@ 83CBB9F61A601CBA00E9B192 = { isa = PBXGroup; children = ( + DC09D54926CCEF1000A3B18B /* src */, 13B07FAE1A68108700A75B9A /* RealmReactNativeTests */, 832341AE1AAA6A7D00B99B32 /* Libraries */, 00E356EF1AD99517003FC87E /* RealmReactNativeTestsTests */, @@ -585,6 +587,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; + EXPANDED_CODE_SIGN_IDENTITY = ""; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -656,7 +659,7 @@ ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = ""; - GCC_C_LANGUAGE_STANDARD = gnu99; + EXPANDED_CODE_SIGN_IDENTITY = ""; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; diff --git a/integration-tests/environments/react-native/package-lock.json b/integration-tests/environments/react-native/package-lock.json index 438e66c932..1c3f49c1e8 100644 --- a/integration-tests/environments/react-native/package-lock.json +++ b/integration-tests/environments/react-native/package-lock.json @@ -759,7 +759,10 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" @@ -863,6 +866,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", "dependencies": { + "@babel/helper-module-transforms": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" }, "engines": { @@ -881,7 +885,7 @@ "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@babel/core": "^7.0.0" } }, "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { @@ -11034,7 +11038,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.18.6" } }, "@babel/plugin-syntax-class-static-block": { @@ -11102,6 +11106,7 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", "requires": { + "@babel/helper-module-transforms": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" } }, diff --git a/integration-tests/environments/react-native/src/App.js b/integration-tests/environments/react-native/src/App.js index 9359d0dae5..695ca65ae0 100644 --- a/integration-tests/environments/react-native/src/App.js +++ b/integration-tests/environments/react-native/src/App.js @@ -39,6 +39,7 @@ ErrorUtils.setGlobalHandler((err, isFatal) => { }); const mode = typeof DedicatedWorkerGlobalScope === "undefined" ? "native" : "chrome-debugging"; +const engine = global.HermesInternal ? "hermes" : "jsc"; export class App extends Component { state = { status: "disconnected" }; @@ -153,7 +154,7 @@ export class App extends Component { prepareTests() { this.client = new Client({ - title: `React-Native on ${Platform.OS} (${mode})`, + title: `React-Native on ${Platform.OS} (${mode} using ${engine})`, tests: (context) => { /* eslint-env mocha */ if (typeof context.mode === "string" && context.mode !== mode) { diff --git a/integration-tests/tests/src/schemas/person-and-dogs.ts b/integration-tests/tests/src/schemas/person-and-dogs.ts index 9b9e21a10b..8a6bfcdc88 100644 --- a/integration-tests/tests/src/schemas/person-and-dogs.ts +++ b/integration-tests/tests/src/schemas/person-and-dogs.ts @@ -38,16 +38,13 @@ export const PersonSchema: Realm.ObjectSchema = { }; export class Person extends Realm.Object { - name: string; - age: number; + name!: string; + age!: number; friends!: Realm.List; dogs!: Realm.Collection; - constructor(name: string, age: number) { - super(); - - this.name = name; - this.age = age; + constructor(realm: Realm, name: string, age: number) { + super(realm, { name, age }); } static schema: Realm.ObjectSchema = PersonSchema; @@ -69,16 +66,12 @@ export const DogSchema: Realm.ObjectSchema = { }; export class Dog extends Realm.Object { - name: string; - age: number; - owner: Person; - - constructor(name: string, age: number, owner: Person) { - super(); + name!: string; + age!: number; + owner!: Person; - this.name = name; - this.age = age; - this.owner = owner; + constructor(realm: Realm, name: string, age: number, owner: Person) { + super(realm, { name, age, owner }); } static schema: Realm.ObjectSchema = DogSchema; diff --git a/integration-tests/tests/src/tests/class-models.ts b/integration-tests/tests/src/tests/class-models.ts new file mode 100644 index 0000000000..582e4476aa --- /dev/null +++ b/integration-tests/tests/src/tests/class-models.ts @@ -0,0 +1,121 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 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. +// +//////////////////////////////////////////////////////////////////////////// + +import { expect } from "chai"; +import Realm from "realm"; + +import { openRealmBeforeEach } from "../hooks"; + +describe("Class models", () => { + describe("as schema element", () => { + beforeEach(() => { + Realm.clearTestState(); + }); + + it("fails without a schema static", () => { + class Person extends Realm.Object {} + expect(() => { + new Realm({ schema: [Person as any] }); + }).throws("must have a 'schema' property"); + }); + + it("fails without a schema.properties static", () => { + class Person extends Realm.Object { + static schema = { name: "Person" }; + } + expect(() => { + new Realm({ schema: [Person as any] }); + }).throws("properties must be of type 'object'"); + }); + + it("fails if it doesn't extend Realm.Object", () => { + class Person { + name!: string; + static schema: Realm.ObjectSchema = { + name: "Person", + properties: { name: "string" }, + }; + } + expect(() => { + new Realm({ schema: [Person as any] }); + }).throws("Class 'Person' must extend Realm.Object"); + + // Mutate the name of the object schema to produce a more detailed error + Person.schema.name = "Foo"; + expect(() => { + new Realm({ schema: [Person as any] }); + }).throws("Class 'Person' (declaring 'Foo' schema) must extend Realm.Object"); + }); + + it("is allowed", () => { + class Person extends Realm.Object { + name!: string; + static schema: Realm.ObjectSchema = { + name: "Person", + properties: { name: "string" }, + }; + } + new Realm({ schema: [Person] }); + }); + }); + + describe("#constructor", () => { + // The Pick and Partial is needed to correctly reflect the defaults + class Person extends Realm.Object & Partial> { + id!: Realm.BSON.ObjectId; + name!: string; + age!: number; + friends!: Realm.List; + + static schema: Realm.ObjectSchema = { + name: "Person", + properties: { + id: { + type: "objectId", + default: new Realm.BSON.ObjectId(), // TODO: Make this a function + }, + name: "string", + age: { + type: "int", + default: 32, + }, + friends: "Person[]", + }, + }; + } + + openRealmBeforeEach({ schema: [Person] }); + + it("creates objects with values", function (this: RealmContext) { + this.realm.write(() => { + // Expect no persons in the database + const persons = this.realm.objects("Person"); + expect(persons.length).equals(0); + + const alice = new Person(this.realm, { name: "Alice" }); + expect(alice.name).equals("Alice"); + // Expect the first element to be the object we just added + expect(persons.length).equals(1); + expect(persons[0]._objectId()).equals(alice._objectId()); + expect(persons[0].name).equals("Alice"); + // Property value fallback to the default + expect(persons[0].age).equals(32); + }); + }); + }); +}); diff --git a/integration-tests/tests/src/tests/index.ts b/integration-tests/tests/src/tests/index.ts index 63412f2aea..c053cabc01 100644 --- a/integration-tests/tests/src/tests/index.ts +++ b/integration-tests/tests/src/tests/index.ts @@ -22,8 +22,9 @@ import chai from "chai"; chai.use(chaiAsPromised); import "./realm-constructor"; -import "./serialization"; import "./objects"; +import "./class-models"; +import "./serialization"; import "./iterators"; import "./queries"; import "./dynamic-schema-updates"; diff --git a/integration-tests/tests/src/tests/serialization.ts b/integration-tests/tests/src/tests/serialization.ts index e3baae7851..b45467653a 100755 --- a/integration-tests/tests/src/tests/serialization.ts +++ b/integration-tests/tests/src/tests/serialization.ts @@ -143,7 +143,7 @@ const testSetups: TestSetup[] = [ }, }, { - name: "Class models (NO primaryKey)", + name: "Class model (NO primaryKey)", schema: [PlaylistNoId, SongNoId], testData(realm: Realm) { realm.write(() => { @@ -287,7 +287,7 @@ const testSetups: TestSetup[] = [ }, }, { - name: "Class models (Int primaryKey)", + name: "Class model (Int primaryKey)", schema: [PlaylistWithId, SongWithId], testData(realm: Realm) { realm.write(() => { diff --git a/integration-tests/tests/src/tests/sync/flexible.ts b/integration-tests/tests/src/tests/sync/flexible.ts index 879b57ceb5..03dec532b0 100644 --- a/integration-tests/tests/src/tests/sync/flexible.ts +++ b/integration-tests/tests/src/tests/sync/flexible.ts @@ -526,9 +526,10 @@ describe.skipIf(environment.missingServer, "Flexible sync", function () { const subs = this.realm.subscriptions; subs.update((mutableSubs) => { - expect(() => - ((mutableSubs as unknown) as Realm.App.Sync.SubscriptionSet).waitForSynchronization(), - ).to.throw("mutableSubs.waitForSynchronization is not a function"); + // @ts-expect-error Calling a missing function + expect(() => (mutableSubs as Realm.App.Sync.SubscriptionSet).waitForSynchronization()).to.throw( + "mutableSubs.waitForSynchronization is not a function", + ); }); }); }); @@ -801,7 +802,8 @@ describe.skipIf(environment.missingServer, "Flexible sync", function () { it("mutating methods do not exist on non-mutable SubscriptionSet instances", function (this: RealmContext) { const subscriptionInfo = addSubscriptionForPerson(this.realm); - const subsAsMutable = (subscriptionInfo.subs as unknown) as Realm.App.Sync.MutableSubscriptionSet; + // @ts-expect-error Calling missing functions + const subsAsMutable = subscriptionInfo.subs as Realm.App.Sync.MutableSubscriptionSet; const calls = [ () => subsAsMutable.add(this.realm.objects(FlexiblePersonSchema.name)), @@ -848,11 +850,12 @@ describe.skipIf(environment.missingServer, "Flexible sync", function () { await expect( subs.update((mutableSubs) => { - ((mutableSubs as unknown) as Realm.App.Sync.SubscriptionSet).update(() => { + // @ts-expect-error Calling a missing function + (mutableSubs as Realm.App.Sync.SubscriptionSet).update(() => { // This should throw }); }), - ).to.be.rejectedWith("mutableSubs.update is not a function"); + ).to.be.rejectedWith("is not a function"); }); }); diff --git a/lib/browser/.eslintrc.json b/lib/browser/.eslintrc.json deleted file mode 100644 index c27c7a110e..0000000000 --- a/lib/browser/.eslintrc.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "env": { - "worker": true, - "node": false - }, - "globals": { - "global": true - }, - "parserOptions": { - "ecmaFeatures": { - "forOf": false - }, - "sourceType": "module" - }, - "rules": { - "no-console": 0, - "strict": 0 - } -} diff --git a/lib/browser/app.js b/lib/browser/app.js deleted file mode 100644 index 02261b39f2..0000000000 --- a/lib/browser/app.js +++ /dev/null @@ -1,70 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { keys, objectTypes } from "./constants"; -import { createMethods, getterForProperty } from "./util"; -import { setVersions, createAppRPC } from "./rpc"; -import { promisify } from "../utils"; - -function setupApp(app, info) { - app[keys.id] = info.id; - app[keys.realm] = "(App object)"; - app[keys.type] = objectTypes.APP; -} - -export default class App { - constructor(config) { - let info = createAppRPC(config); - setupApp(this, info); - } - - /** - * Invokes the RPC client to set versions. - * @todo Turn this into a call to the static App._setVersions method if the RPC layer supported invoking remote static methods. - * @param {object} versions An object containing package and platform names and versions. - */ - static _setVersions(versions) { - return setVersions(versions); - } - - logIn(credentials) { - return promisify((cb) => this._logIn(credentials, cb)); - } -} - -createMethods(App.prototype, objectTypes.APP, ["_logIn", "switchUser"], true); - -Object.defineProperties(App.prototype, { - currentUser: { get: getterForProperty("currentUser") }, - allUsers: { get: getterForProperty("allUsers") }, - emailPasswordAuth: { get: getterForProperty("emailPasswordAuth") }, -}); - -export function createApp(realmId, info) { - const appProxy = Object.create(App.prototype); - - // FIXME: This is currently necessary because util/createMethod expects - // the realm id to be present on any object that is used over rpc - appProxy[keys.realm] = "(App object)"; - - appProxy[keys.id] = info.id; - appProxy[keys.type] = objectTypes.APP; - Object.assign(appProxy, info.data); - - return appProxy; -} diff --git a/lib/browser/async-open-task.js b/lib/browser/async-open-task.js deleted file mode 100644 index e420cfb7d0..0000000000 --- a/lib/browser/async-open-task.js +++ /dev/null @@ -1,32 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2019 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { keys, objectTypes } from "./constants"; -import { createMethods } from "./util"; - -export default class AsyncOpenTask {} - -createMethods(AsyncOpenTask.prototype, objectTypes.ASYNCOPENTASK, ["addDownloadNotification", "cancel"]); - -export function createAsyncOpenTask(realmId, info) { - let task = Object.create(AsyncOpenTask.prototype); - task[keys.realm] = "(AsyncOpenTask object)"; - task[keys.id] = info.id; - task[keys.type] = objectTypes.ASYNCOPENTASK; - return task; -} diff --git a/lib/browser/base64.js b/lib/browser/base64.js deleted file mode 100644 index 60fe2bede3..0000000000 --- a/lib/browser/base64.js +++ /dev/null @@ -1,84 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -const CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; -const CHAR_MAP = {}; - -Array.from(CHARS, (char, i) => (CHAR_MAP[char] = i)); - -export function decode(base64) { - let length = base64.length; - let byteCount = length * 0.75; - - if (base64[length - 1] === "=") { - byteCount--; - if (base64[length - 2] === "=") { - byteCount--; - } - } - - let buffer = new ArrayBuffer(byteCount); - let bytes = new Uint8Array(buffer); - - for (let i = 0, j = 0; i < length; i += 4) { - let index1 = CHAR_MAP[base64[i]]; - let index2 = CHAR_MAP[base64[i + 1]]; - let index3 = CHAR_MAP[base64[i + 2]]; - let index4 = CHAR_MAP[base64[i + 3]]; - - bytes[j++] = (index1 << 2) + ((index2 & 0x30) >> 4); - bytes[j++] = ((index2 & 0x0f) << 4) + ((index3 & 0x3c) >> 2); - bytes[j++] = ((index3 & 0x03) << 6) + index4; - } - - return buffer; -} - -export function encode(data) { - var byteOffset = 0; - var buffer; - - if (data instanceof ArrayBuffer) { - buffer = data; - } else if (ArrayBuffer.isView(data)) { - buffer = data.buffer; - byteOffset = data.byteOffset; - } else { - throw new TypeError("Can only base64 encode ArrayBuffer and ArrayBufferView objects"); - } - - let byteCount = data.byteLength; - let bytes = new Uint8Array(buffer, byteOffset, byteCount); - let base64 = ""; - - for (let i = 0; i < byteCount; i += 3) { - base64 += CHARS[(bytes[i] & 0xfc) >> 2]; - base64 += CHARS[((bytes[i] & 0x03) << 4) + ((bytes[i + 1] & 0xf0) >> 4)]; - base64 += CHARS[((bytes[i + 1] & 0x0f) << 2) + ((bytes[i + 2] & 0xc0) >> 6)]; - base64 += CHARS[bytes[i + 2] & 0x3f]; - } - - switch (byteCount % 3) { - case 1: - return base64.slice(0, -2) + "=="; - case 2: - return base64.slice(0, -1) + "="; - default: - return base64; - } -} diff --git a/lib/browser/cache.js b/lib/browser/cache.js deleted file mode 100644 index 7a87e9daa4..0000000000 --- a/lib/browser/cache.js +++ /dev/null @@ -1,35 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 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. -// -//////////////////////////////////////////////////////////////////////////// - -export let propertyCache = {}; - -export function invalidateCache(realmId) { - if (realmId) { - propertyCache[realmId] = {}; - } else { - propertyCache = {}; - } -} - -export function getRealmCache(realmId) { - let realmCache = propertyCache[realmId]; - if (!realmCache) { - realmCache = propertyCache[realmId] = {}; - } - return realmCache; -} diff --git a/lib/browser/collections.js b/lib/browser/collections.js deleted file mode 100644 index 50450b7c0f..0000000000 --- a/lib/browser/collections.js +++ /dev/null @@ -1,106 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { keys } from "./constants"; -import * as util from "./util"; -import * as rpc from "./rpc"; -import { invalidateCache } from "./cache"; - -export default class Collection { - constructor() { - throw new TypeError("Illegal constructor"); - } -} - -function isIndex(propertyName) { - return typeof propertyName === "number" || (typeof propertyName === "string" && /^-?\d+$/.test(propertyName)); -} - -const mutable = Symbol("mutable"); - -const traps = { - get(collection, property) { - if (isIndex(property)) { - return util.getProperty(collection, property); - } - - return Reflect.get(collection, property, collection); - }, - set(collection, property, value) { - if (isIndex(property)) { - if (!collection[mutable]) { - return false; - } - - invalidateCache(collection[keys.realm]); - rpc.setProperty(collection[keys.realm], collection[keys.id], property, value); - return true; - } - - if (!Reflect.set(collection, property, value, collection)) { - throw new TypeError(`Cannot assign to read only property '${property}'`); - } - return true; - }, - ownKeys(collection) { - return Reflect.ownKeys(collection).concat(Array.from({ length: collection.length }, (value, key) => String(key))); - }, - getOwnPropertyDescriptor(collection, property) { - if (isIndex(property)) { - let descriptor = { - enumerable: true, - configurable: true, - writable: collection[mutable], - }; - Reflect.defineProperty(descriptor, "value", { get: () => this.get(collection, property) }); - return descriptor; - } - - return Reflect.getOwnPropertyDescriptor(collection, property); - }, - has(collection, property) { - if (isIndex(property)) { - return true; - } - - return Reflect.has(collection, property); - }, -}; - -export function createCollection(prototype, realmId, info, _mutable) { - let collection = Object.create(prototype); - - Object.defineProperties(collection, { - length: { - get: util.getterForProperty("length"), - }, - type: { - value: info.dataType, - }, - optional: { - value: info.optional, - }, - }); - - collection[keys.realm] = realmId; - collection[keys.id] = info.id; - collection[keys.type] = info.type; - collection[mutable] = _mutable; - - return new Proxy(collection, traps); -} diff --git a/lib/browser/constants.js b/lib/browser/constants.js deleted file mode 100644 index 409cd75647..0000000000 --- a/lib/browser/constants.js +++ /dev/null @@ -1,61 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -export const keys = {}; -export const objectTypes = {}; -export const propTypes = {}; - -["id", "realm", "type"].forEach(function (name) { - keys[name] = Symbol(name); -}); - -[ - "DATA", - "DATE", - "DICT", - "ERROR", - "FUNCTION", - "LIST", - "SET", - "DICTIONARY", - "OBJECT", - "REALM", - "RESULTS", - "USER", - "SESSION", - "ASYNCOPENTASK", - "APP", - "CREDENTIALS", - "FETCHRESPONSEHANDLER", - "UNDEFINED", - "EMAILPASSWORDAUTH", - "EJSON", -].forEach(function (type) { - Object.defineProperty(objectTypes, type, { - value: type.toLowerCase(), - }); -}); - -["BOOL", "INT", "FLOAT", "DOUBLE", "DECIMAL", "STRING", "DATE", "DATA", "OBJECT", "LIST", "OBJECTID"].forEach(function ( - type, -) { - Object.defineProperty(propTypes, type, { - value: type.toLowerCase(), - enumerable: true, - }); -}); diff --git a/lib/browser/credentials.js b/lib/browser/credentials.js deleted file mode 100644 index 51c45e83f5..0000000000 --- a/lib/browser/credentials.js +++ /dev/null @@ -1,82 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { keys, objectTypes } from "./constants"; -import { - _anonymousRPC, - _facebookRPC, - _functionRPC, - _googleRPC, - _appleRPC, - _emailPasswordRPC, - _userApiKeyRPC, - _serverApiKeyRPC, - _jwtRPC, -} from "./rpc"; - -export default class Credentials { - static anonymous() { - return _anonymousRPC(); - } - - static facebook(token) { - return _facebookRPC(token); - } - - static apple(token) { - return _appleRPC(token); - } - - static emailPassword(email, password) { - return _emailPasswordRPC(email, password); - } - - static userApiKey(user_key) { - return _userApiKeyRPC(user_key); - } - - static function(payload) { - return _functionRPC(payload); - } - - static serverApiKey(server_key) { - return _serverApiKeyRPC(server_key); - } - - static google(authCode) { - return _googleRPC(authCode); - } - - static jwt(token) { - return _jwtRPC(token); - } -} - -export function createCredentials(realmId, info) { - const credentialsProxy = Object.create(Credentials.prototype); - - // FIXME: This is currently necessary because util/createMethod expects - // the realm id to be present on any object that is used over rpc - credentialsProxy[keys.realm] = "(Credentials object)"; - - credentialsProxy[keys.id] = info.id; - credentialsProxy[keys.type] = objectTypes.CREDENTIALS; - Object.assign(credentialsProxy, info.data); - - return credentialsProxy; -} diff --git a/lib/browser/dictionaries.js b/lib/browser/dictionaries.js deleted file mode 100644 index 10ed29c494..0000000000 --- a/lib/browser/dictionaries.js +++ /dev/null @@ -1,29 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 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. -// -//////////////////////////////////////////////////////////////////////////// - -import Collection, { createCollection } from "./collections"; - -export default class Dictionary extends Collection { - constructor() { - throw new Error("Dictionaries are not yet supported in Chrome debugging mode"); - } -} - -export function createDictionary(realmId, info) { - return createCollection(Dictionary.prototype, realmId, info, true); -} diff --git a/lib/browser/email-password-auth.js b/lib/browser/email-password-auth.js deleted file mode 100644 index fb9073f4c0..0000000000 --- a/lib/browser/email-password-auth.js +++ /dev/null @@ -1,99 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { keys, objectTypes } from "./constants"; -import { createMethods } from "./util"; -import { promisify } from "../utils"; -import { handleDeprecatedPositionalArgs } from "@realm.io/common"; - -// TODO for v11: change all signatures to methodName(argsObject) and remove handleDeprecatedPositionalArgs call -// -// Example post-v11: -// registerUser(argsObject) { -// return promisify((cb) => this._registerUser(argsObject, cb)); -// }, -export class EmailPasswordAuth { - registerUser(...args) { - const { argsObject } = handleDeprecatedPositionalArgs(args, "registerUser", ["email", "password"]); - - return promisify((cb) => this._registerUser(argsObject, cb)); - } - - confirmUser(...args) { - const { argsObject } = handleDeprecatedPositionalArgs(args, "confirmUser", ["token", "tokenId"]); - - return promisify((cb) => this._confirmUser(argsObject, cb)); - } - - resendConfirmationEmail(...args) { - const { argsObject } = handleDeprecatedPositionalArgs(args, "resendConfirmationEmail", ["email"]); - - return promisify((cb) => this._resendConfirmationEmail(argsObject, cb)); - } - - retryCustomConfirmation(...args) { - const { argsObject } = handleDeprecatedPositionalArgs(args, "retryCustomConfirmation", ["email"]); - - return promisify((cb) => this._retryCustomConfirmation(argsObject, cb)); - } - - sendResetPasswordEmail(...args) { - const { argsObject } = handleDeprecatedPositionalArgs(args, "sendResetPasswordEmail", ["email"]); - - return promisify((cb) => this._sendResetPasswordEmail(argsObject, cb)); - } - - resetPassword(...args) { - const { argsObject } = handleDeprecatedPositionalArgs(args, "resetPassword", ["password", "token", "tokenId"]); - - return promisify((cb) => this._resetPassword(argsObject, cb)); - } - - callResetPasswordFunction(...args) { - const { argsObject, restArgs } = handleDeprecatedPositionalArgs( - args, - "callResetPasswordFunction", - ["email", "password"], - true, - ); - - return promisify((cb) => this._callResetPasswordFunction(argsObject, restArgs, cb)); - } -} - -createMethods(EmailPasswordAuth.prototype, objectTypes.EMAILPASSWORDAUTH, [ - "_registerUser", - "_confirmUser", - "_resendConfirmationEmail", - "_retryCustomConfirmation", - "_sendResetPasswordEmail", - "_resetPassword", - "_callResetPasswordFunction", -]); - -export function createEmailPasswordAuth(realmId, info) { - const proxy = Object.create(EmailPasswordAuth.prototype); - - // FIXME: This is currently necessary because util/createMethod expects - // the realm id to be present on any object that is used over rpc - proxy[keys.realm] = "(EmailPasswordAuth object)"; - proxy[keys.id] = info.id; - proxy[keys.type] = objectTypes.EMAILPASSWORDAUTH; - - return proxy; -} diff --git a/lib/browser/fetch.js b/lib/browser/fetch.js deleted file mode 100644 index c51ff671c4..0000000000 --- a/lib/browser/fetch.js +++ /dev/null @@ -1,64 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { objectTypes } from "./constants"; -import { callMethod, registerTypeConverter } from "./rpc"; - -export function performFetch(request, responseHandler) { - const { url, ...init } = request; - if (typeof url !== "string") { - throw new Error("Expected a URL"); - } - if (typeof responseHandler !== "object") { - throw new Error("Expected a response handler object"); - } - const { onSuccess, onError } = responseHandler; - // Delegate to fetch - fetch(url, init) - .then(async (response) => { - const decodedBody = await response.text(); - // Pull out the headers of the response - const headers = {}; - response.headers.forEach((value, key) => { - headers[key] = value; - }); - return { - statusCode: response.status, - headers, - body: decodedBody, - }; - }) - .then(onSuccess, onError); -} - -function deserializeResponseHandler(realmId, info) { - const { id } = info; - if (typeof id !== "number") { - throw new Error("Expected a nummeric id"); - } - return { - onSuccess: function () { - callMethod(undefined, id, "onSuccess", Array.from(arguments)); - }, - onError: function () { - callMethod(undefined, id, "onError", Array.from(arguments)); - }, - }; -} - -registerTypeConverter(objectTypes.FETCHRESPONSEHANDLER, deserializeResponseHandler); diff --git a/lib/browser/index.js b/lib/browser/index.js deleted file mode 100644 index 41780c0545..0000000000 --- a/lib/browser/index.js +++ /dev/null @@ -1,242 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { NativeModules } from "react-native"; -import { keys, objectTypes } from "./constants"; -import Collection from "./collections"; -import List, { createList } from "./lists"; -import Set, { createSet } from "./sets"; -import Dictionary, { createDictionary } from "./dictionaries"; -import Results, { createResults } from "./results"; -import RealmObject, * as objects from "./objects"; -import User, { createUser } from "./user"; -import { createAsyncOpenTask } from "./async-open-task"; -import App, { createApp } from "./app"; -import Credentials, { createCredentials } from "./credentials"; -import * as rpc from "./rpc"; -import * as util from "./util"; -import { createSession } from "./session"; -import { invalidateCache } from "./cache"; -import { performFetch } from "./fetch"; -import { createEmailPasswordAuth } from "./email-password-auth"; - -const { debugHosts, debugPort } = NativeModules.Realm; - -rpc.registerTypeConverter(objectTypes.LIST, createList); -rpc.registerTypeConverter(objectTypes.SET, createSet); -rpc.registerTypeConverter(objectTypes.DICTIONARY, createDictionary); -rpc.registerTypeConverter(objectTypes.RESULTS, createResults); -rpc.registerTypeConverter(objectTypes.OBJECT, objects.createObject); -rpc.registerTypeConverter(objectTypes.REALM, createRealm); -rpc.registerTypeConverter(objectTypes.USER, createUser); -rpc.registerTypeConverter(objectTypes.SESSION, createSession); -rpc.registerTypeConverter(objectTypes.ASYNCOPENTASK, createAsyncOpenTask); -rpc.registerTypeConverter(objectTypes.APP, createApp); -rpc.registerTypeConverter(objectTypes.CREDENTIALS, createCredentials); -rpc.registerTypeConverter(objectTypes.EMAILPASSWORDAUTH, createEmailPasswordAuth); - -function createRealm(_, info) { - let realm = Object.create(Realm.prototype); - setupRealm(realm, info); - return realm; -} - -function setupRealm(realm, info) { - realm[keys.id] = info.id; - realm[keys.realm] = info.realmId; - realm[keys.type] = objectTypes.REALM; - - ["empty", "schema", "schemaVersion", "isInTransaction", "isClosed"].forEach((name) => { - Object.defineProperty(realm, name, { get: util.getterForProperty(name) }); - }); - for (let key in info.data) { - realm[key] = rpc.deserialize(info.id, info.data[key]); - } -} - -function getObjectType(realm, type) { - if (typeof type == "function") { - return objects.typeForConstructor(realm[keys.realm], type); - } - return type; -} - -export default class Realm { - constructor(config) { - let schemas = typeof config === "object" && config.schema; - let constructors = schemas ? {} : null; - - for (let i = 0, len = schemas ? schemas.length : 0; i < len; i++) { - let item = schemas[i]; - - if (typeof item == "function") { - let schema = item.schema; - if (!schema || typeof schema != "object") { - throw new Error("Realm object constructor must have a 'schema' property."); - } - - let { name, properties } = schema; - if (!name || typeof name != "string") { - 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( - `Failed to read ObjectSchema: properties must be of type 'object', got (${typeof properties})`, - ); - } - - schemas.splice(i, 1, schema); - constructors[name] = item; - } - } - - let info = rpc.createRealm(Array.from(arguments)); - setupRealm(this, info); - - // This will create mappings between the id, path, and potential constructors. - objects.registerConstructors(info.realmId, this.path, constructors); - } - - create(type, ...args) { - let method = util.createMethod(objectTypes.REALM, "create", true); - return method.apply(this, [getObjectType(this, type), ...args]); - } - - objects(type, ...args) { - let method = util.createMethod(objectTypes.REALM, "objects"); - return method.apply(this, [getObjectType(this, type), ...args]); - } - - objectForPrimaryKey(type, ...args) { - let method = util.createMethod(objectTypes.REALM, "objectForPrimaryKey"); - return method.apply(this, [getObjectType(this, type), ...args]); - } -} - -// Non-mutating methods: -util.createMethods(Realm.prototype, objectTypes.REALM, [ - "addListener", - "removeListener", - "removeAllListeners", - "writeCopyTo", - "_waitForDownload", -]); - -// Mutating methods: -util.createMethods( - Realm.prototype, - objectTypes.REALM, - [ - "delete", - "deleteModel", - "deleteAll", - "write", - "compact", - "close", - "beginTransaction", - "commitTransaction", - "cancelTransaction", - ], - true, -); - -Object.defineProperties(Realm, { - Collection: { - value: Collection, - }, - List: { - value: List, - }, - Set: { - value: Set, - }, - Dictionary: { - value: Dictionary, - }, - Results: { - value: Results, - }, - Object: { - value: RealmObject, - }, - App: { - value: App, - }, - Credentials: { - value: Credentials, - }, - User: { - value: User, - }, - defaultPath: { - get: util.getterForProperty("defaultPath", false), - set: util.setterForProperty("defaultPath"), - }, - schemaVersion: { - value: function () { - return rpc.callMethod(undefined, Realm[keys.id], "schemaVersion", Array.from(arguments)); - }, - }, - deleteFile: { - value: function () { - return rpc.callMethod(undefined, Realm[keys.id], "deleteFile", Array.from(arguments)); - }, - }, - copyBundledRealmFiles: { - value: function () { - return rpc.callMethod(undefined, Realm[keys.id], "copyBundledRealmFiles", []); - }, - }, - clearTestState: { - value: function () { - objects.clearRegisteredConstructors(); - invalidateCache(); - rpc.clearTestState(); - }, - }, - _asyncOpen: { - value: function (config, callback) { - return rpc.asyncOpenRealm(Realm[keys.id], config, callback); - }, - }, - exists: { - value: function () { - return rpc.callMethod(undefined, Realm[keys.id], "exists", Array.from(arguments)); - }, - }, -}); - -for (let i = 0, len = debugHosts.length; i < len; i++) { - try { - Realm[keys.id] = rpc.createSession(debugHosts[i] + ":" + debugPort, { performFetch }); - break; - } catch (e) { - // Only throw exception after all hosts have been tried. - if (i < len - 1) { - continue; - } - - // Log the original exception for debugging purposes. - console.error(e); - - throw new Error( - "Realm failed to connect to the embedded debug server inside the app. " + - "If attempting to use Chrome debugging from a device, ensure the device is " + - "reachable on the same network as this machine.", - ); - } -} diff --git a/lib/browser/lists.js b/lib/browser/lists.js deleted file mode 100644 index acc5bc2f16..0000000000 --- a/lib/browser/lists.js +++ /dev/null @@ -1,47 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -import Collection, { createCollection } from "./collections"; -import { objectTypes } from "./constants"; -import { createMethods } from "./util"; - -export default class List extends Collection {} - -// Non-mutating methods: -createMethods(List.prototype, objectTypes.LIST, [ - "filtered", - "sorted", - "snapshot", - "isValid", - "isEmpty", - "indexOf", - "min", - "max", - "sum", - "avg", - "addListener", - "removeListener", - "removeAllListeners", -]); - -// Mutating methods: -createMethods(List.prototype, objectTypes.LIST, ["pop", "shift", "push", "unshift", "splice"], true); - -export function createList(realmId, info) { - return createCollection(List.prototype, realmId, info, true); -} diff --git a/lib/browser/objects.js b/lib/browser/objects.js deleted file mode 100644 index 015e022962..0000000000 --- a/lib/browser/objects.js +++ /dev/null @@ -1,98 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { keys, objectTypes } from "./constants"; -import { getterForProperty, setterForProperty, createMethods, cacheObject } from "./util"; -import * as rpc from "./rpc"; - -let registeredConstructors = {}; -let registeredRealmPaths = {}; - -export default class RealmObject {} - -// Non-mutating methods: -createMethods(RealmObject.prototype, objectTypes.OBJECT, [ - "isValid", - "objectSchema", - "linkingObjects", - "linkingObjectsCount", - "_objectId", - "_isSameObject", - "addListener", - "removeListener", - "removeAllListeners", - "getPropertyType", -]); - -export function clearRegisteredConstructors() { - registeredConstructors = {}; - registeredRealmPaths = {}; -} - -export function createObject(realmId, info) { - let schema = info.schema; - let realmPath = registeredRealmPaths[realmId]; - let constructor = (registeredConstructors[realmPath] || {})[schema.name]; - let object = Object.create(constructor ? constructor.prototype : RealmObject.prototype); - - object[keys.realm] = realmId; - object[keys.id] = info.id; - object[keys.type] = info.type; - - schema.properties.forEach((name) => { - Object.defineProperty(object, name, { - enumerable: true, - get: getterForProperty(name), - set: setterForProperty(name), - }); - }); - - if (constructor) { - let result = constructor.call(object); - if (result != null && result != object) { - throw new Error("Realm object constructor must not return another value"); - } - } - for (let key in info.cache) { - info.cache[key] = rpc.deserialize(undefined, info.cache[key]); - } - cacheObject(realmId, info.id, info.cache); - - return object; -} - -export function registerConstructors(realmId, realmPath, constructors) { - registeredRealmPaths[realmId] = realmPath; - - if (constructors) { - registeredConstructors[realmPath] = constructors; - } -} - -export function typeForConstructor(realmId, constructor) { - let realmPath = registeredRealmPaths[realmId]; - let constructors = registeredConstructors[realmPath]; - - for (let name in constructors) { - if (constructors[name] == constructor) { - return name; - } - } - - throw new Error("Constructor was not registered in the schema for this Realm"); -} diff --git a/lib/browser/results.js b/lib/browser/results.js deleted file mode 100644 index 5c472032ec..0000000000 --- a/lib/browser/results.js +++ /dev/null @@ -1,48 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -import Collection, { createCollection } from "./collections"; -import { objectTypes } from "./constants"; -import { createMethods } from "./util"; - -export default class Results extends Collection {} - -// Non-mutating methods: -createMethods(Results.prototype, objectTypes.RESULTS, [ - "description", - "filtered", - "sorted", - "snapshot", - "isValid", - "isEmpty", - "indexOf", - "min", - "max", - "sum", - "avg", - "addListener", - "removeListener", - "removeAllListeners", -]); - -// Mutating methods: -createMethods(Results.prototype, objectTypes.RESULTS, ["update"], true); - -export function createResults(realmId, info) { - return createCollection(Results.prototype, realmId, info); -} diff --git a/lib/browser/rpc.js b/lib/browser/rpc.js deleted file mode 100644 index d3ee756d08..0000000000 --- a/lib/browser/rpc.js +++ /dev/null @@ -1,407 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { EJSON, ObjectId, Decimal128 } from "bson"; - -import * as base64 from "./base64"; -import { keys, objectTypes } from "./constants"; -import { invalidateCache } from "./cache"; - -const { id: idKey } = keys; -let registeredCallbacks = []; -const typeConverters = {}; - -// Callbacks that are registered initially (currently only refreshAccessToken) will -// carry this symbol so they are not wiped in clearTestState. -const persistentCallback = Symbol("persistentCallback"); - -let XMLHttpRequest = global.originalXMLHttpRequest || global.XMLHttpRequest; -let sessionHost; -let sessionId; - -// Check if XMLHttpRequest has been overridden, and get the native one if that's the case. -if (XMLHttpRequest.__proto__ != global.XMLHttpRequestEventTarget) { - let fakeXMLHttpRequest = XMLHttpRequest; - delete global.XMLHttpRequest; - XMLHttpRequest = global.XMLHttpRequest; - global.XMLHttpRequest = fakeXMLHttpRequest; -} - -registerTypeConverter(objectTypes.DATA, (_, { value }) => base64.decode(value)); -registerTypeConverter(objectTypes.DATE, (_, { value }) => new Date(value)); -registerTypeConverter(objectTypes.EJSON, (_, { value }) => EJSON.deserialize(value)); -registerTypeConverter(objectTypes.DICT, deserializeDict); -registerTypeConverter(objectTypes.ERROR, deserializeError); -registerTypeConverter(objectTypes.FUNCTION, deserializeFunction); - -export function registerTypeConverter(type, handler) { - typeConverters[type] = handler; -} - -function beforeNotify(realm) { - // NOTE: the mere existence of this function is important for read - // isolation even independent of what it does in its body. By having a - // beforenotify listener, we ensure that the RPC server can't proceed in - // notify() to autorefresh until the browser performs a callback poll. - // Without this, the RPC server could autorefresh in between two subsequent - // property reads from the browser. - - // Clear the cache for this Realm, and reenable caching if it was disabled - // by a write transaction. - invalidateCache(realm[keys.realm]); -} - -export function createSession(host, { versions, performFetch }) { - sessionHost = host; - - sessionId = sendRequest( - "create_session", - { - versions, - fetch: serialize(undefined, performFetch), - }, - host, - ); - - return sessionId; -} - -export function createRealm(args) { - if (args) { - args = args.map((arg) => serialize(null, arg)); - } - - return sendRequest("create_realm", { arguments: args, beforeNotify: serialize(null, beforeNotify) }); -} - -export function createAppRPC(config) { - return sendRequest("create_app", { arguments: [serialize(null, config)] }); -} - -export function asyncOpenRealm(id, config, callback) { - return deserialize( - undefined, - sendRequest("call_method", { - id, - name: "_asyncOpen", - arguments: [ - serialize(null, config), - serialize(null, (realm, error) => { - if (realm) { - realm.addListener("beforenotify", beforeNotify); - } - callback(realm, error); - }), - ], - }), - ); -} - -export function setVersions(versions) { - sendRequest("set_versions", { versions: serialize(null, versions) }); -} - -export function _anonymousRPC() { - const result = sendRequest("_anonymous", { arguments: undefined }); - return deserialize(undefined, result); -} - -export function _appleRPC() { - const args = Array.prototype.map.call(arguments, (arg) => serialize(null, arg)); - const result = sendRequest("_apple", { arguments: args }); - return deserialize(undefined, result); -} - -export function _emailPasswordRPC() { - const args = Array.prototype.map.call(arguments, (arg) => serialize(null, arg)); - const result = sendRequest("_emailPassword", { arguments: args }); - return deserialize(undefined, result); -} - -export function _facebookRPC() { - const args = Array.prototype.map.call(arguments, (arg) => serialize(null, arg)); - const result = sendRequest("_facebook", { arguments: args }); - return deserialize(undefined, result); -} - -export function _functionRPC() { - const args = Array.prototype.map.call(arguments, (arg) => serialize(null, arg)); - const result = sendRequest("_function", { arguments: args }); - return deserialize(undefined, result); -} - -export function _googleRPC() { - const args = Array.prototype.map.call(arguments, (arg) => serialize(null, arg)); - const result = sendRequest("_google", { arguments: args }); - return deserialize(undefined, result); -} -export function _userApiKeyRPC() { - const args = Array.prototype.map.call(arguments, (arg) => serialize(null, arg)); - const result = sendRequest("_userAPIKey", { arguments: args }); - return deserialize(undefined, result); -} - -export function _serverApiKeyRPC() { - const args = Array.prototype.map.call(arguments, (arg) => serialize(null, arg)); - const result = sendRequest("_serverAPIKey", { arguments: args }); - return deserialize(undefined, result); -} - -export function _jwtRPC() { - const args = Array.prototype.map.call(arguments, (arg) => serialize(null, arg)); - const result = sendRequest("_jwt", { arguments: args }); - return deserialize(undefined, result); -} - -export function callSyncFunction(name, args) { - args = (args || []).map((arg) => serialize(null, arg)); - let result = sendRequest("call_sync_function", { name, arguments: args }); - return deserialize(undefined, result); -} - -export function callMethod(realmId, id, name, args) { - if (!Array.isArray(args)) { - throw new Error("Expected an array of arguments"); - } - const serializedArgs = args.map((arg) => serialize(realmId, arg)); - const result = sendRequest("call_method", { realmId, id, name, arguments: serializedArgs }); - return deserialize(realmId, result); -} - -export function getObject(realmId, id, name) { - let result = sendRequest("get_object", { realmId, id, name }); - if (!result) { - return result; - } - for (let key in result) { - result[key] = deserialize(realmId, result[key]); - } - return result; -} - -export function getProperty(realmId, id, name) { - let result = sendRequest("get_property", { realmId, id, name }); - return deserialize(realmId, result); -} - -export function setProperty(realmId, id, name, value) { - value = serialize(realmId, value); - sendRequest("set_property", { realmId, id, name, value }); -} - -export function clearTestState() { - sendRequest("clear_test_state"); - - // Clear all registered callbacks that are specific to this session. - registeredCallbacks = registeredCallbacks.filter((cb) => Reflect.has(cb, persistentCallback)); -} - -function registerCallback(callback) { - let key = registeredCallbacks.indexOf(callback); - return key >= 0 ? key : registeredCallbacks.push(callback) - 1; -} - -function serialize(realmId, value) { - if (typeof value == "undefined") { - return { type: objectTypes.UNDEFINED }; - } - if (typeof value == "function") { - return { type: objectTypes.FUNCTION, value: registerCallback(value) }; - } - if (!value || typeof value != "object") { - return { value: value }; - } - - let id = value[idKey]; - if (id) { - return { id }; - } - - if (value instanceof Date) { - return { type: objectTypes.DATE, value: value.getTime() }; - } - - if (Array.isArray(value)) { - let array = value.map((item) => serialize(realmId, item)); - return { value: array }; - } - - if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) { - return { type: objectTypes.DATA, value: base64.encode(value) }; - } - - if (value instanceof ObjectId || value instanceof Decimal128) { - return { - type: objectTypes.EJSON, - value: EJSON.serialize(value, { relaxed: false }), - }; - } - - let keys = Object.keys(value); - let values = keys.map((key) => serialize(realmId, value[key])); - return { type: objectTypes.DICT, keys, values }; -} - -export function deserialize(realmId, info) { - let type = info.type; - let handler = type && typeConverters[type]; - if (handler) { - return handler(realmId, info); - } - - let value = info.value; - if (value && Array.isArray(value)) { - return value.map((item) => deserialize(realmId, item)); - } - - return value; -} - -function deserializeDict(realmId, info) { - let { keys, values } = info; - let object = {}; - - for (let i = 0, len = keys.length; i < len; i++) { - object[keys[i]] = deserialize(realmId, values[i]); - } - - return object; -} - -function deserializeError(realmId, info) { - const { message, stack } = info.error; - const err = new Error(message.value); - err.stack = stack.value; - return err; -} - -function deserializeFunction(realmId, info) { - return registeredCallbacks[info.value]; -} - -function makeRequest(url, data) { - let statusCode; - let responseText; - - // The global __debug__ object is provided by Visual Studio Code. - if (global.__debug__) { - let request = global.__debug__.require("sync-request"); - let response = request("POST", url, { - body: JSON.stringify(data), - headers: { - "Content-Type": "text/plain;charset=UTF-8", - }, - }); - - statusCode = response.statusCode; - responseText = response.body.toString("utf-8"); - } else { - let body = JSON.stringify(data); - let request = new XMLHttpRequest(); - - request.open("POST", url, false); - request.send(body); - - statusCode = request.status; - responseText = request.responseText; - } - - if (statusCode != 200) { - throw new Error(responseText); - } - - return JSON.parse(responseText); -} - -let pollTimeoutId; -let pollTimeout = 10; - -function sendRequest(command, data, host = sessionHost) { - clearTimeout(pollTimeoutId); - try { - if (!host) { - throw new Error("Must first create RPC session with a valid host"); - } - - data = Object.assign({}, data, sessionId ? { sessionId } : null); - - let url = "http://" + host + "/" + command; - let response = makeRequest(url, data); - let callback = response && response.callback; - - // Reset the callback poll interval to 10ms every time we either hit a - // callback or call any other method, and double it each time we poll - // for callbacks and get nothing until it's over a second. - if (callback || command !== "callbacks_poll") { - pollTimeout = 10; - } else if (pollTimeout < 1000) { - pollTimeout *= 2; - } - - if (!response || response.error) { - let error = response && response.error; - - // Remove the type prefix from the error message (e.g. "Error: "). - if (error && typeof error === "string") { - error = error.replace(/^[a-z]+: /i, ""); - } else if (error.type && error.type === "error") { - const err = new Error(error.message.value); - err.stack = error.stack.value; - throw err; - } - - throw new Error(error || `Invalid response for "${command}"`); - } - if (callback != null) { - let result, error, stack; - try { - let realmId = data.realmId; - let thisObject = deserialize(realmId, response.this); - let args = deserialize(realmId, response.arguments); - const fn = registeredCallbacks[callback]; - if (fn) { - result = serialize(realmId, fn.apply(thisObject, args)); - } else { - error = `Unknown callback id: ${callback}`; - } - } catch (e) { - error = e.message || "" + e; - if (e.stack) { - stack = JSON.stringify(e.stack); - } - } - - let callbackCommand = "callback_result"; - if (command === "callbacks_poll" || command === "callback_poll_result") { - callbackCommand = "callback_poll_result"; - } - - return sendRequest(callbackCommand, { - callback, - result, - error, - stack, - callback_call_counter: response.callback_call_counter, - }); - } - - return response.result; - } finally { - pollTimeoutId = setTimeout(() => sendRequest("callbacks_poll"), pollTimeout); - } -} diff --git a/lib/browser/session.js b/lib/browser/session.js deleted file mode 100644 index 3abe50810e..0000000000 --- a/lib/browser/session.js +++ /dev/null @@ -1,60 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { keys, objectTypes } from "./constants"; -import { getterForProperty, createMethods } from "./util"; -import { deserialize } from "./rpc"; - -export default class Session {} - -Object.defineProperties(Session.prototype, { - connectionState: { get: getterForProperty("connectionState", false) }, - state: { get: getterForProperty("state", false) }, - url: { get: getterForProperty("url", false) }, -}); - -createMethods(Session.prototype, objectTypes.SESSION, [ - "_refreshAccessToken", - "_simulateError", - "addProgressNotification", - "removeProgressNotification", - "addConnectionNotification", - "removeConnectionNotification", - "isConnected", - "resume", - "pause", - "_waitForDownloadCompletion", - "_waitForUploadCompletion", -]); - -export function createSession(realmId, info) { - let sessionProxy = Object.create(Session.prototype); - - // FIXME: This is currently necessary because util/createMethod expects - // the realm id to be present on any object that is used over rpc - sessionProxy[keys.realm] = "(Session object)"; - - if (info && info.data && info.data.user) { - sessionProxy[keys.id] = info.id; - sessionProxy[keys.type] = objectTypes.SESSION; - sessionProxy.user = deserialize(realmId, info.data.user); - sessionProxy.config = deserialize(realmId, info.data.config); - } - - return sessionProxy; -} diff --git a/lib/browser/sets.js b/lib/browser/sets.js deleted file mode 100644 index 9fa90547ad..0000000000 --- a/lib/browser/sets.js +++ /dev/null @@ -1,29 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2021 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. -// -//////////////////////////////////////////////////////////////////////////// - -import Collection, { createCollection } from "./collections"; - -export default class Set extends Collection { - constructor() { - throw new Error("Sets are not yet supported in Chrome debugging mode"); - } -} - -export function createSet(realmId, info) { - return createCollection(Set.prototype, realmId, info, true); -} diff --git a/lib/browser/user.js b/lib/browser/user.js deleted file mode 100644 index 71d2f3f678..0000000000 --- a/lib/browser/user.js +++ /dev/null @@ -1,90 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { EJSON } from "bson"; - -import { keys, objectTypes } from "./constants"; -import { getterForProperty, createMethods } from "./util"; -import { promisify } from "../utils"; - -export default class User { - logOut() { - return promisify((cb) => this._logOut(cb)); - } - - async callFunction(name, args, service) { - const stringifiedArgs = EJSON.stringify(args, { relaxed: false }); - const result = await promisify((cb) => this._callFunction(name, stringifiedArgs, service, cb)); - return EJSON.parse(result); - } - - get functions() { - return new Proxy(this, { - get(target, name, receiver) { - if (typeof name === "string" && name !== "inspect") { - return function (...args) { - return target.callFunction(name, args); - }; - } else { - return Reflect.get(target, name, receiver); - } - }, - }); - } - - get customData() { - return EJSON.parse(this._customData); - } -} - -createMethods(User.prototype, objectTypes.USER, [ - "_logOut", - "_sessionForOnDiskPath", - "_linkCredentials", - "_callFunction", - "_pushRegister", - "_pushDeregister", - "_makeStreamingRequest", - // "_newWatchStream", // TODO expose WatchStream type via RN debug API -]); - -Object.defineProperties(User.prototype, { - id: { get: getterForProperty("id") }, - accessToken: { get: getterForProperty("accessToken") }, - refreshToken: { get: getterForProperty("refreshToken") }, - profile: { get: getterForProperty("profile") }, - identities: { get: getterForProperty("identities") }, - providerType: { get: getterForProperty("providerType") }, - isLoggedIn: { get: getterForProperty("isLoggedIn") }, - state: { get: getterForProperty("state") }, - apiKeys: { get: getterForProperty("apiKeys") }, - deviceId: { get: getterForProperty("deviceId") }, - _customData: { get: getterForProperty("_customData") }, -}); - -export function createUser(realmId, info) { - const userProxy = Object.create(User.prototype); - - // FIXME: This is currently necessary because util/createMethod expects - // the realm id to be present on any object that is used over rpc - userProxy[keys.realm] = "(User object)"; - userProxy[keys.id] = info.id; - userProxy[keys.type] = objectTypes.USER; - - return userProxy; -} diff --git a/lib/browser/util.js b/lib/browser/util.js deleted file mode 100644 index 155581368b..0000000000 --- a/lib/browser/util.js +++ /dev/null @@ -1,95 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -import { keys } from "./constants"; -import * as rpc from "./rpc"; -import { invalidateCache, getRealmCache } from "./cache"; - -export function createMethods(prototype, type, methodNames, mutating) { - let props = {}; - - methodNames.forEach((name) => { - props[name] = { - value: createMethod(type, name, mutating), - }; - }); - - Object.defineProperties(prototype, props); -} - -export function createMethod(type, name, mutating) { - return function () { - let realmId = this[keys.realm]; - let id = this[keys.id]; - - if (!realmId || !id) { - throw new TypeError(`${type}.${name} was called on non-Realm object ${this}!`); - } - if (this[keys.type] !== type) { - throw new TypeError(`${type}.${name} was called on Realm object of type ${this[keys.type]}!`); - } - - if (mutating) { - invalidateCache(realmId); - } - try { - return rpc.callMethod(realmId, id, name, Array.from(arguments)); - } finally { - if (mutating) { - invalidateCache(realmId); - } - } - }; -} - -export function cacheObject(realmId, id, value) { - getRealmCache(realmId)[id] = value; -} - -export function getProperty(obj, name, cache = true) { - let realmId = obj[keys.realm]; - let id = obj[keys.id]; - if (!cache || realmId === undefined) { - return rpc.getProperty(realmId, id, name); - } - - let realmCache = getRealmCache(realmId); - let objCache = realmCache[id]; - if (!objCache) { - objCache = realmCache[id] = rpc.getObject(realmId, id, name); - return objCache[name]; - } - - if (name in objCache) { - return objCache[name]; - } - return (objCache[name] = rpc.getProperty(realmId, id, name)); -} - -export function getterForProperty(name, cache = true) { - return function () { - return getProperty(this, name, cache); - }; -} - -export function setterForProperty(name) { - return function (value) { - invalidateCache(this[keys.realm]); - rpc.setProperty(this[keys.realm], this[keys.id], name, value); - }; -} diff --git a/lib/collection-methods.js b/lib/collection-methods.js index a2cce57503..76471b5e65 100644 --- a/lib/collection-methods.js +++ b/lib/collection-methods.js @@ -48,7 +48,19 @@ module.exports = function (realmConstructor) { ].forEach(function (methodName) { var method = arrayPrototype[methodName]; if (method) { - exportedFunctions[methodName] = { value: method, configurable: true, writable: true }; + exportedFunctions[methodName] = { + // XXX this is a temporary solution/hack to work around the fact that Hermes' implementation of Array does not + // like it when you call one of its methods bound to a non-array. We were calling them on our Collection types. + // This code is just copying the collection to a real array, then calling the method on that. Instead, we should + // find a way to operate on the collection directly, possibly by using our own implementation of the array methods, + // possibly copied from a polyfill lib. This is particularly bad for things like find() that may not need to look + // at the whole collection but we are copying the whole thing anyway. + value(...args) { + return [...this][methodName](...args); + }, + configurable: true, + writable: true, + }; } }); diff --git a/lib/extensions.js b/lib/extensions.js index a10f3e64f3..04106e899c 100644 --- a/lib/extensions.js +++ b/lib/extensions.js @@ -62,7 +62,7 @@ module.exports = function (realmConstructor) { const { DefaultNetworkTransport } = require("realm-network-transport"); realmConstructor._networkTransport = new DefaultNetworkTransport(); Object.defineProperty(realmConstructor.Collection.prototype, "toJSON", { - value: function (_, cache = new Map()) { + value: function toJSON(_, cache = new Map()) { return this.map((item, index) => item instanceof realmConstructor.Object ? item.toJSON(index.toString(), cache) : item, ); diff --git a/lib/index.js b/lib/index.js index f3594d3a80..9461744116 100644 --- a/lib/index.js +++ b/lib/index.js @@ -18,39 +18,12 @@ const utils = require("./utils"); -// Prevent React Native packager from seeing modules required with this -const nodeRequire = require; - -function getRealmConstructor(environment) { - switch (environment) { - case "node.js": - case "electron": - return nodeRequire("bindings")("realm.node").Realm; - case "reactnative": - //switch how babel transpiled code creates children objects. - //Inheriting from Realm.Object with class syntax does not support using Reflect.construct the way babel transpiles it. - //Defining Reflect.construct.sham makes the transpiled code use different standard mechanism for inheriting. (Function.apply with setPrototypeOf) - if (typeof Reflect !== "undefined" && Reflect.construct) { - Reflect.construct.sham = 1; - } - return global.Realm; - case "jscore": - return global.Realm; - case "chromedebugger": - case "vscodedebugger": - // This condition is for stripping "browser" folder from production bundles. - if (global.__DEV__) { - return require("./browser").default; // (exported as ES6 module) - } else { - throw new Error("Can´t use debugger if __DEV__ isn´t true."); - } - default: - throw new Error("Unexpected execution environment (" + environment + ")"); - } +const environment = utils.getEnvironment(); +if (environment !== "node.js" && environment !== "electron") { + throw new Error(`Unexpected execution environment (${environment})`); } -const environment = utils.getEnvironment(); -const realmConstructor = getRealmConstructor(environment); +const realmConstructor = require("bindings")("realm.node").Realm; require("./extensions")(realmConstructor, environment); diff --git a/src/jsc/jsc_context.hpp b/lib/index.native.js similarity index 71% rename from src/jsc/jsc_context.hpp rename to lib/index.native.js index d73f5eb3a7..8d85c4e607 100644 --- a/src/jsc/jsc_context.hpp +++ b/lib/index.native.js @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2016 Realm Inc. +// Copyright 2022 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,18 +16,5 @@ // //////////////////////////////////////////////////////////////////////////// -#pragma once - -#include "jsc_types.hpp" - -namespace realm { -namespace js { - -template <> -inline JSGlobalContextRef jsc::Context::get_global_context(JSContextRef ctx) -{ - return JSContextGetGlobalContext(ctx); -} - -} // namespace js -} // namespace realm +const { Realm } = require("./react-native"); +module.exports = Realm; diff --git a/lib/react-native/.eslintrc.json b/lib/react-native/.eslintrc.json new file mode 100644 index 0000000000..69944eabf4 --- /dev/null +++ b/lib/react-native/.eslintrc.json @@ -0,0 +1,6 @@ +{ + "extends": [ + "@react-native-community", + "../../.eslintrc" + ] +} \ No newline at end of file diff --git a/lib/react-native/index.js b/lib/react-native/index.js new file mode 100644 index 0000000000..993a3da4bb --- /dev/null +++ b/lib/react-native/index.js @@ -0,0 +1,60 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2021 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. +// +//////////////////////////////////////////////////////////////////////////// + +import { Platform, NativeModules } from "react-native"; + +import utils from "../utils"; +import extend from "../extensions"; + +//switch how babel transpiled code creates children objects. +//Inheriting from Realm.Object with class syntax does not support using Reflect.construct the way babel transpiles it. +//Defining Reflect.construct.sham makes the transpiled code use different standard mechanism for inheriting. (Function.apply with setPrototypeOf) +if (typeof Reflect !== "undefined" && Reflect.construct) { + Reflect.construct.sham = 1; +} + +const usingLegacyChromeDebugger = typeof DedicatedWorkerGlobalScope !== "undefined"; + +if (usingLegacyChromeDebugger) { + throw new Error("This version of Realm JS doesn't support the legacy Chrome Debugger. Please use Flipper instead."); +} + +if (Platform.OS === "android") { + // Getting the native module on Android will inject the Realm global + // eslint-disable-next-line no-unused-vars + const RealmNativeModule = NativeModules.Realm; +} + +// TODO: Remove the need to store Realm as a global +// @see https://github.com/realm/realm-js/issues/2126 +// eslint-disable-next-line no-restricted-globals +export const Realm = globalThis.Realm; + +// Otherwise, we must be in a "normal" react native situation. +// In that case, the Realm type should have been injected by the native code. +// If it hasn't, the user likely forgot to install the RealmJS CocoaPod +if (typeof Realm === "undefined") { + throw new Error( + 'Missing Realm constructor. Did you run "pod install"? Please see https://docs.mongodb.com/realm/sdk/react-native/install/ for troubleshooting', + ); +} + +extend(Realm); + +const versions = utils.getVersions(); +Realm.App._setVersions(versions); diff --git a/package-lock.json b/package-lock.json index fb51097a00..fe4b550266 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "realm", - "version": "10.21.0", + "version": "11.0.0-rc.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "realm", - "version": "10.21.0", + "version": "11.0.0-rc.1", "hasInstallScript": true, "license": "See the actual license in the file LICENSE", "dependencies": { @@ -59,7 +59,7 @@ "mockery": "^2.0.0", "prebuild": "^10.0.1", "prettier": "^2.0.4", - "react-native": "^0.69.1", + "react-native": "0.69.1", "rimraf": "^2.6.3", "semver": "^5.6.0", "shelljs": "^0.8.5", @@ -72,7 +72,7 @@ "npm": ">=7" }, "peerDependencies": { - "react-native": ">=0.64" + "react-native": ">=0.66.0" }, "peerDependenciesMeta": { "react-native": { diff --git a/package.json b/package.json index d4c16356ca..2663ad31b7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "realm", "description": "Realm is a mobile database: an alternative to SQLite and key-value stores", - "version": "10.21.0", + "version": "11.0.0-rc.1", "license": "See the actual license in the file LICENSE", "homepage": "https://realm.io", "keywords": [ @@ -31,14 +31,13 @@ }, "types": "types/index.d.ts", "main": "lib/index.js", + "react-native": "lib/index.native.js", "files": [ "cmake", "lib", "types", "react-native", "scripts", - "src", - "vendor", "dependencies.list", "react-native.config.js", "RealmJS.podspec", @@ -104,7 +103,7 @@ "url-parse": "^1.4.4" }, "peerDependencies": { - "react-native": ">=0.64" + "react-native": ">=0.66.0" }, "peerDependenciesMeta": { "react-native": { @@ -140,7 +139,7 @@ "mockery": "^2.0.0", "prebuild": "^10.0.1", "prettier": "^2.0.4", - "react-native": "^0.69.1", + "react-native": "0.69.1", "rimraf": "^2.6.3", "semver": "^5.6.0", "shelljs": "^0.8.5", diff --git a/packages/realm-app-importer/package.json b/packages/realm-app-importer/package.json index 11d0c49c55..8710e61d48 100644 --- a/packages/realm-app-importer/package.json +++ b/packages/realm-app-importer/package.json @@ -13,7 +13,7 @@ "build-cli": "tsc -p tsconfig.build.json", "watch": "rollup -w --config", "lint": "eslint --ext .js,.ts .", - "prepare": "rm -rf dist && npm run build", + "prepare": "npm run build", "start": "ts-node src/cli.ts" }, "files": [ diff --git a/packages/realm-common/package.json b/packages/realm-common/package.json index 91a5025d40..97a1eb1518 100644 --- a/packages/realm-common/package.json +++ b/packages/realm-common/package.json @@ -15,7 +15,7 @@ "build": "npm run generate-types && rollup --config", "lint": "eslint --ext .js,.ts .", "test": "mocha 'src/**/*.test.ts'", - "prepare": "rm -rf dist && npm run build" + "prepare": "npm run build" }, "files": [ "dist" diff --git a/packages/realm-react/package.json b/packages/realm-react/package.json index b1abf1e388..6c65085438 100644 --- a/packages/realm-react/package.json +++ b/packages/realm-react/package.json @@ -39,7 +39,8 @@ "prettier": "^2.3.2", "react": "^17.0.2", "react-native": "^0.65.1", - "react-test-renderer": "^17.0.2" + "react-test-renderer": "^17.0.2", + "realm": "*" }, "peerDependencies": { "react": ">=16.8.0", diff --git a/packages/realm-react/src/cachedObject.ts b/packages/realm-react/src/cachedObject.ts index bfaead4a80..9295aab823 100644 --- a/packages/realm-react/src/cachedObject.ts +++ b/packages/realm-react/src/cachedObject.ts @@ -21,11 +21,11 @@ import { createCachedCollection } from "./cachedCollection"; /** * Arguments object for `cachedObject`. */ -type CachedObjectArgs = { +type CachedObjectArgs = { /** * The {@link Realm.Object} to proxy */ - object: T | null; + object: Realm.Object | null; /** * The {@link Realm} instance */ @@ -49,11 +49,11 @@ type CachedObjectArgs = { * @param args - {@link CachedObjectArgs} object arguments * @returns Proxy object wrapping the {@link Realm.Object} */ -export function createCachedObject({ +export function createCachedObject({ object, realm, updateCallback, -}: CachedObjectArgs): { object: T | null; tearDown: () => void } { +}: CachedObjectArgs): { object: Realm.Object | null; tearDown: () => void } { const listCaches = new Map(); const listTearDowns: Array<() => void> = []; // If the object doesn't exist, just return it with an noop tearDown @@ -64,7 +64,7 @@ export function createCachedObject({ // This Proxy handler intercepts any accesses into properties of the cached object // of type `Realm.List`, and returns a `cachedCollection` wrapping those properties // to allow changes in the list to trigger re-renders - const cachedObjectHandler: ProxyHandler = { + const cachedObjectHandler: ProxyHandler = { get: function (target, key) { const value = Reflect.get(target, key); // Pass methods through @@ -92,14 +92,14 @@ export function createCachedObject({ }; const cachedObjectResult = new Proxy(object, cachedObjectHandler); - const listenerCallback: Realm.ObjectChangeCallback = (obj, changes) => { + const listenerCallback: Realm.ObjectChangeCallback = (obj, changes) => { if (changes.deleted) { updateCallback(); } else if (changes.changedProperties.length > 0) { // Don't force a second re-render if any of the changed properties is a Realm.List, // as the List's cachedCollection will force a re-render itself const anyListPropertyModified = changes.changedProperties.some((property) => { - return obj[property as keyof T] instanceof Realm.List; + return obj[property] instanceof Realm.List; }); const shouldRerender = !anyListPropertyModified; diff --git a/packages/realm-react/src/useObject.tsx b/packages/realm-react/src/useObject.tsx index 57c4ac88b5..8af5825e1d 100644 --- a/packages/realm-react/src/useObject.tsx +++ b/packages/realm-react/src/useObject.tsx @@ -45,7 +45,10 @@ export function createUseObject(useRealm: () => Realm) { * @param primaryKey - The primary key of the desired object which will be retrieved using {@link Realm.objectForPrimaryKey} * @returns either the desired {@link Realm.Object} or `null` in the case of it being deleted or not existing. */ - return function useObject(type: string | { new (): T }, primaryKey: PrimaryKey): (T & Realm.Object) | null { + return function useObject( + type: string | { new (...args: any): T }, + primaryKey: PrimaryKey, + ): (T & Realm.Object) | null { const realm = useRealm(); // Create a forceRerender function for the cachedObject to use as its updateCallback, so that @@ -58,7 +61,7 @@ export function createUseObject(useRealm: () => Realm) { // When this is implemented, remove `?? null` () => createCachedObject({ - object: realm.objectForPrimaryKey(type, primaryKey) ?? null, + object: realm.objectForPrimaryKey(type, primaryKey) ?? null, realm, updateCallback: forceRerender, }), @@ -76,6 +79,6 @@ export function createUseObject(useRealm: () => Realm) { } // Wrap object in a proxy to update the reference on rerender ( should only rerender when something has changed ) - return new Proxy(object, {}); + return new Proxy(object, {}) as T & Realm.Object; }; } diff --git a/packages/realm-react/src/useQuery.tsx b/packages/realm-react/src/useQuery.tsx index 8fb14dc0b6..d5a5ed87b4 100644 --- a/packages/realm-react/src/useQuery.tsx +++ b/packages/realm-react/src/useQuery.tsx @@ -48,7 +48,9 @@ export function createUseQuery(useRealm: () => Realm) { * @param type - The object type, depicted by a string or a class extending Realm.Object * @returns a collection of realm objects or an empty array */ - return function useQuery(type: string | ({ new (): T } & Realm.ObjectClass)): Realm.Results { + return function useQuery( + type: string | ({ new (...args: any): T } & Realm.ObjectClass), + ): Realm.Results { const realm = useRealm(); // Create a forceRerender function for the cachedCollection to use as its updateCallback, so that diff --git a/packages/realm-web/src/App.ts b/packages/realm-web/src/App.ts index e5cdb4913f..7d11894b85 100644 --- a/packages/realm-web/src/App.ts +++ b/packages/realm-web/src/App.ts @@ -61,8 +61,9 @@ export interface AppConfiguration extends Realm.AppConfiguration { */ export class App< FunctionsFactoryType = Realm.DefaultFunctionsFactory & Realm.BaseFunctionsFactory, - CustomDataType = SimpleObject -> implements Realm.App { + CustomDataType = SimpleObject, +> implements Realm.App +{ /** * A map of app instances returned from calling getApp. */ diff --git a/packages/realm-web/src/Fetcher.ts b/packages/realm-web/src/Fetcher.ts index d1a6965893..55e6b0312e 100644 --- a/packages/realm-web/src/Fetcher.ts +++ b/packages/realm-web/src/Fetcher.ts @@ -292,7 +292,7 @@ export class Fetcher implements LocationUrlContext { const responseBody = await response.json(); return deserialize(responseBody as SimpleObject) as ResponseBody; } else if (contentType === null) { - return (null as unknown) as ResponseBody; + return null as unknown as ResponseBody; } else { throw new Error(`Expected JSON response, got "${contentType}"`); } diff --git a/packages/realm-web/src/User.ts b/packages/realm-web/src/User.ts index dcf89835c7..8a5652cc11 100644 --- a/packages/realm-web/src/User.ts +++ b/packages/realm-web/src/User.ts @@ -77,8 +77,9 @@ export enum UserType { export class User< FunctionsFactoryType = Realm.DefaultFunctionsFactory, CustomDataType = SimpleObject, - UserProfileDataType = Realm.DefaultUserProfileData -> implements Realm.User { + UserProfileDataType = Realm.DefaultUserProfileData, +> implements Realm.User +{ /** * The app that this user is associated with. */ @@ -114,7 +115,7 @@ export class User< * @param parameters Parameters of the user. */ public constructor(parameters: HydratableUserParameters | UserParameters) { - this.app = (parameters.app as App) as App; + this.app = parameters.app as App as App; this.id = parameters.id; this.storage = new UserStorage(this.app.storage, this.id); if ("accessToken" in parameters && "refreshToken" in parameters && "providerType" in parameters) { @@ -138,7 +139,7 @@ export class User< } } this.fetcher = this.app.fetcher.clone({ - userContext: { currentUser: (this as unknown) as User }, + userContext: { currentUser: this as unknown as User }, }); this.apiKeys = new ApiKeyAuth(this.fetcher); this.functions = FunctionsFactory.create(this.fetcher) as FunctionsFactoryType & Realm.BaseFunctionsFactory; @@ -277,7 +278,7 @@ export class User< /** @inheritdoc */ public async linkCredentials(credentials: Credentials): Promise { - const response = await this.app.authenticator.authenticate(credentials, (this as unknown) as User); + const response = await this.app.authenticator.authenticate(credentials, this as unknown as User); // Sanity check the response if (this.id !== response.userId) { const details = `got user id ${response.userId} expected ${this.id}`; diff --git a/packages/realm-web/src/auth-providers/EmailPasswordAuth.ts b/packages/realm-web/src/auth-providers/EmailPasswordAuth.ts index 41603f4801..79e8ba4513 100644 --- a/packages/realm-web/src/auth-providers/EmailPasswordAuth.ts +++ b/packages/realm-web/src/auth-providers/EmailPasswordAuth.ts @@ -152,15 +152,13 @@ export class EmailPasswordAuth implements Realm.Auth.EmailPasswordAuth { async callResetPasswordFunction( ...args: [string, string, ...unknown[]] | [Realm.Auth.CallResetPasswordFunctionDetails, ...unknown[]] ): Promise { - const { - argsObject: resetDetails, - restArgs, - } = handleDeprecatedPositionalArgs( - args, - "callResetPasswordFunction", - ["email", "password"], - true, - ); + const { argsObject: resetDetails, restArgs } = + handleDeprecatedPositionalArgs( + args, + "callResetPasswordFunction", + ["email", "password"], + true, + ); const appRoute = this.fetcher.appRoute; await this.fetcher.fetchJSON({ diff --git a/react-native/android/build.gradle b/react-native/android/build.gradle index 13c35ddce1..5bd196b38e 100644 --- a/react-native/android/build.gradle +++ b/react-native/android/build.gradle @@ -21,25 +21,6 @@ allprojects { apply plugin: 'com.android.library' -tasks.register('forwardDebugPort', Exec) { - def adb = android.getAdbExe()?.toString() ?: 'false' - commandLine adb, 'forward', 'tcp:8083', 'tcp:8083' - ignoreExitValue true - doLast { - if (execResult.getExitValue() != 0) { - logger.error( - '===========================================================================\n' + - 'WARNING: Failed to automatically forward port 8083.\n' + - 'In order to use Realm in Chrome debugging mode, port 8083 must be forwarded\n' + - 'from localhost to the device or emulator being used to run the application.\n' + - 'You may need to add the appropriate flags to the command that failed:\n' + - ' adb forward tcp:8083 tcp:8083\n' + - '===========================================================================\n' - ) - } - } -} - android { compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : 28 buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : '28.0.3' @@ -48,9 +29,10 @@ android { minSdkVersion rootProject.hasProperty('minSdkVersion') ? rootProject.minSdkVersion : 16 targetSdkVersion rootProject.hasProperty('targetSdkVersion') ? rootProject.targetSdkVersion : 28 } - - tasks.withType(JavaCompile) { - compileTask -> compileTask.dependsOn forwardDebugPort + buildTypes { + debug { + jniDebuggable true + } } // Do not strip debug symbols from debug builds of Realm @@ -76,6 +58,5 @@ try { project.dependencies { add(dependencyType, fileTree(dir: 'libs', include: ['*.jar'])) - add(dependencyType, 'org.nanohttpd:nanohttpd:2.2.0') add(dependencyType, 'com.facebook.react:react-native:+') } diff --git a/react-native/android/src/main/java/io/realm/react/RealmReactModule.java b/react-native/android/src/main/java/io/realm/react/RealmReactModule.java index e45e03e83f..c57b1348ec 100644 --- a/react-native/android/src/main/java/io/realm/react/RealmReactModule.java +++ b/react-native/android/src/main/java/io/realm/react/RealmReactModule.java @@ -22,6 +22,7 @@ import android.os.HandlerThread; import android.util.Log; +import com.facebook.react.bridge.JavaScriptContextHolder; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.turbomodule.core.CallInvokerHolderImpl; @@ -40,13 +41,10 @@ import java.util.Map; import java.util.Locale; -import fi.iki.elonen.NanoHTTPD; - class RealmReactModule extends ReactContextBaseJavaModule { - private static final int DEFAULT_PORT = 8083; + public static final String NAME = "Realm"; private static boolean sentAnalytics = false; - private AndroidWebServer webServer; // used to create a native AssetManager in C++ in order to load file from APK // Note: We keep a VM reference to the assetManager to prevent its being // garbage collected while the native object is in use. @@ -57,17 +55,14 @@ class RealmReactModule extends ReactContextBaseJavaModule { static { try { SoLoader.loadLibrary("realm"); - } catch (UnsatisfiedLinkError e) { - if (e.getMessage().contains("library \"libjsc.so\" not found")) { - throw new RuntimeException("Realm JS does not support the Hermes engine yet. Express your 💚 on https://github.com/realm/realm-js/issues/2455", e); + } catch (UnsatisfiedLinkError err) { + if (err.getMessage().contains("library \"libjsi.so\" not found")) { + throw new LinkageError("This version of Realm JS needs at least React Native version 0.66.0", err); } - throw e; + throw err; } } - private Handler worker; - private HandlerThread workerThread; - public RealmReactModule(ReactApplicationContext reactContext) { super(reactContext); @@ -81,6 +76,16 @@ public RealmReactModule(ReactApplicationContext reactContext) { } setDefaultRealmFileDirectory(fileDir, assetManager); + + // Get the javascript runtime and install our native module with it + + // TODO: Update this to use reactContext.getRuntimeExecutor() instead (since this is calling a deprecated method underneath) + // Using the RuntimeExecutor however, requires that we link our native module against fbjni. + + JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder(); + synchronized(jsContext) { + install(jsContext.get()); + } } @Override @@ -93,190 +98,26 @@ public void initialize() { } @Override - public String getName() { - return "Realm"; + public void invalidate() { + invalidateCaches(); } @Override - public Map getConstants() { - if (isContextInjected()) { - // No constants are needed if *not* running in Chrome debug mode. - return Collections.emptyMap(); - } - - startWebServer(); - - List hosts; - if (isRunningOnEmulator()) { - hosts = Arrays.asList("localhost"); - } else { - hosts = getIPAddresses(); - } - - HashMap constants = new HashMap(); - constants.put("debugHosts", hosts); - constants.put("debugPort", DEFAULT_PORT); - return constants; + public String getName() { + return NAME; } @Override - public void onCatalystInstanceDestroy() { - clearContextInjectedFlag(); - stopWebServer(); - } - - private static boolean isRunningOnEmulator() { - // This list matched the list in package 'react-native-device-info' (see RNDeviceInfo/RNDeviceModule.java@isEmulatorSync) - return Build.FINGERPRINT.startsWith("generic") - || Build.FINGERPRINT.startsWith("unknown") - || Build.MODEL.contains("google_sdk") - || Build.MODEL.toLowerCase(Locale.ROOT).contains("droid4x") - || Build.MODEL.contains("Emulator") - || Build.MODEL.contains("Android SDK built for x86") - || Build.MANUFACTURER.contains("Genymotion") - || Build.HARDWARE.contains("goldfish") - || Build.HARDWARE.contains("ranchu") - || Build.HARDWARE.contains("vbox86") - || Build.PRODUCT.contains("sdk") - || Build.PRODUCT.contains("google_sdk") - || Build.PRODUCT.contains("sdk_google") - || Build.PRODUCT.contains("sdk_x86") - || Build.PRODUCT.contains("vbox86p") - || Build.PRODUCT.contains("emulator") - || Build.PRODUCT.contains("simulator") - || Build.BOARD.toLowerCase(Locale.ROOT).contains("nox") - || Build.BOOTLOADER.toLowerCase(Locale.ROOT).contains("nox") - || Build.HARDWARE.toLowerCase(Locale.ROOT).contains("nox") - || Build.PRODUCT.toLowerCase(Locale.ROOT).contains("nox") - || Build.SERIAL.toLowerCase(Locale.ROOT).contains("nox") - || (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")); - } - - private List getIPAddresses() { - ArrayList ipAddresses = new ArrayList(); - - try { - Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); - - while (networkInterfaces.hasMoreElements()) { - NetworkInterface networkInterface = networkInterfaces.nextElement(); - if (networkInterface.isLoopback() || !networkInterface.isUp()) { - continue; - } - - Enumeration addresses = networkInterface.getInetAddresses(); - while (addresses.hasMoreElements()) { - InetAddress address = addresses.nextElement(); - if (address.isLoopbackAddress() || address.isLinkLocalAddress() || address.isAnyLocalAddress()) { - continue; - } - - ipAddresses.add(address.getHostAddress()); - } - } - } catch (SocketException e) { - e.printStackTrace(); - } - - return ipAddresses; - } - - private void startWebServer() { - setupChromeDebugModeRealmJsContext(); - startWorker(); - - webServer = new AndroidWebServer(DEFAULT_PORT, getReactApplicationContext()); - try { - webServer.start(); - Log.i("Realm", "Starting the debugging WebServer, Host: " + webServer.getHostname() + " Port: " + webServer.getListeningPort()); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private void startWorker() { - workerThread = new HandlerThread("MyHandlerThread"); - workerThread.start(); - worker = new Handler(workerThread.getLooper()); - worker.postDelayed(new Runnable() { - @Override - public void run() { - boolean stop = tryRunTask(); - if (!stop) { - worker.postDelayed(this, 10); - } - } - }, 10); - } - - private void stopWebServer() { - if (webServer != null) { - Log.i("Realm", "Stopping the webserver"); - webServer.stop(); - } - - if (workerThread != null) { - workerThread.quit(); - workerThread = null; - } - } - - class AndroidWebServer extends NanoHTTPD { - private ReactApplicationContext reactApplicationContext; - - public AndroidWebServer(int port, ReactApplicationContext reactApplicationContext) { - super(port); - this.reactApplicationContext = reactApplicationContext; - } - - public AndroidWebServer(String hostname, int port, ReactApplicationContext reactApplicationContext) { - super(hostname, port); - this.reactApplicationContext = reactApplicationContext; - } - - @Override - public Response serve(IHTTPSession session) { - final String cmdUri = session.getUri(); - final HashMap map = new HashMap(); - try { - session.parseBody(map); - } catch (IOException e) { - e.printStackTrace(); - } catch (ResponseException e) { - e.printStackTrace(); - } - final String json = map.get("postData"); - if (json == null) { - Response response = newFixedLengthResponse(""); - response.addHeader("Access-Control-Allow-Origin", "http://localhost:8081"); - return response; - } - final String jsonResponse = processChromeDebugCommand(cmdUri, json); - - Response response = newFixedLengthResponse(jsonResponse); - response.addHeader("Access-Control-Allow-Origin", "http://localhost:8081"); - return response; - } + public Map getConstants() { + return Collections.emptyMap(); } - // return true if the Realm API was injected (return false when running in Chrome Debug) - private native boolean isContextInjected(); - - // clear the flag set when injecting Realm API - private native void clearContextInjectedFlag(); - // fileDir: path of the internal storage of the application private native void setDefaultRealmFileDirectory(String fileDir, AssetManager assets); - // responsible for creating the rpcServer that will accept the chrome Websocket command - private native long setupChromeDebugModeRealmJsContext(); - - // this receives one command from Chrome debug then return the processing we should post back - private native String processChromeDebugCommand(String cmd, String args); - - // this receives one command from Chrome debug then return the processing we should post back - private native boolean tryRunTask(); + private native void install(long runtimePointer); + private native void invalidateCaches(); // Passes the React Native jsCallInvokerHolder over to C++ so we can setup our UI queue flushing private native void setupFlushUiQueue(CallInvokerHolderImpl callInvoker); } diff --git a/react-native/android/src/main/java/io/realm/react/RealmReactPackage.java b/react-native/android/src/main/java/io/realm/react/RealmReactPackage.java index a722b05954..8c075428d4 100644 --- a/react-native/android/src/main/java/io/realm/react/RealmReactPackage.java +++ b/react-native/android/src/main/java/io/realm/react/RealmReactPackage.java @@ -16,27 +16,45 @@ package io.realm.react; +import android.util.Log; + import com.facebook.react.ReactPackage; -import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.TurboReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.uimanager.ViewManager; +import com.facebook.react.module.model.ReactModuleInfo; +import com.facebook.react.module.model.ReactModuleInfoProvider; -import java.util.Collections; -import java.util.List; +import java.util.HashMap; +import java.util.Map; -public class RealmReactPackage implements ReactPackage { +public class RealmReactPackage extends TurboReactPackage implements ReactPackage { @Override - public List createNativeModules(ReactApplicationContext reactContext) { - return Collections.singletonList(new RealmReactModule(reactContext)); - } - - public List> createJSModules() { - return Collections.emptyList(); + public NativeModule getModule(String name, final ReactApplicationContext reactContext) { + if (name.equals(RealmReactModule.NAME)) { + return new RealmReactModule(reactContext); + } else { + return null; + } } @Override - public List createViewManagers(ReactApplicationContext reactContext) { - return Collections.emptyList(); + public ReactModuleInfoProvider getReactModuleInfoProvider() { + return new ReactModuleInfoProvider() { + @Override + public Map getReactModuleInfos() { + Map reactModuleInfoMap = new HashMap(); + reactModuleInfoMap.put(RealmReactModule.NAME, new ReactModuleInfo( + RealmReactModule.NAME, + "io.realm.react.RealmReactModule", + false, + false, + false, + false, // TODO: Should we be using this? + true + )); + return reactModuleInfoMap; + } + }; } } diff --git a/react-native/ios/RealmReact.xcodeproj/project.pbxproj b/react-native/ios/RealmReact.xcodeproj/project.pbxproj index a288c12d1a..8bed60946f 100644 --- a/react-native/ios/RealmReact.xcodeproj/project.pbxproj +++ b/react-native/ios/RealmReact.xcodeproj/project.pbxproj @@ -8,35 +8,12 @@ /* Begin PBXBuildFile section */ 85B3572725FEB5B9003A02D4 /* realm-js-ios.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 85B3572625FEB5B9003A02D4 /* realm-js-ios.xcframework */; }; - 85B358EA25FF80CF003A02D4 /* GCDWebServerResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358CE25FF80CF003A02D4 /* GCDWebServerResponse.m */; }; - 85B358EB25FF80CF003A02D4 /* GCDWebServerRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358D125FF80CF003A02D4 /* GCDWebServerRequest.m */; }; - 85B358EC25FF80CF003A02D4 /* GCDWebServerFunctions.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358D425FF80CF003A02D4 /* GCDWebServerFunctions.m */; }; - 85B358ED25FF80CF003A02D4 /* GCDWebServer.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358D525FF80CF003A02D4 /* GCDWebServer.m */; }; - 85B358EE25FF80CF003A02D4 /* GCDWebServerConnection.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358D625FF80CF003A02D4 /* GCDWebServerConnection.m */; }; - 85B358EF25FF80CF003A02D4 /* GCDWebServerErrorResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358DB25FF80CF003A02D4 /* GCDWebServerErrorResponse.m */; }; - 85B358F025FF80CF003A02D4 /* GCDWebServerFileResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358DD25FF80CF003A02D4 /* GCDWebServerFileResponse.m */; }; - 85B358F125FF80CF003A02D4 /* GCDWebServerDataResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358DE25FF80CF003A02D4 /* GCDWebServerDataResponse.m */; }; - 85B358F225FF80CF003A02D4 /* GCDWebServerStreamedResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358E025FF80CF003A02D4 /* GCDWebServerStreamedResponse.m */; }; - 85B358F325FF80CF003A02D4 /* GCDWebServerURLEncodedFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358E525FF80CF003A02D4 /* GCDWebServerURLEncodedFormRequest.m */; }; - 85B358F425FF80CF003A02D4 /* GCDWebServerMultiPartFormRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358E625FF80CF003A02D4 /* GCDWebServerMultiPartFormRequest.m */; }; - 85B358F525FF80CF003A02D4 /* GCDWebServerDataRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358E725FF80CF003A02D4 /* GCDWebServerDataRequest.m */; }; - 85B358F625FF80CF003A02D4 /* GCDWebServerFileRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 85B358E825FF80CF003A02D4 /* GCDWebServerFileRequest.m */; }; - 85B358F925FF80DB003A02D4 /* libGCDWebServer.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 85B358BB25FF8070003A02D4 /* libGCDWebServer.a */; }; F60690171CA2766F0003FB26 /* RealmReact.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = F60690161CA2766F0003FB26 /* RealmReact.h */; }; F60690191CA2766F0003FB26 /* RealmReact.mm in Sources */ = {isa = PBXBuildFile; fileRef = F60690181CA2766F0003FB26 /* RealmReact.mm */; }; F60690211CA277410003FB26 /* RealmAnalytics.mm in Sources */ = {isa = PBXBuildFile; fileRef = F60690201CA277410003FB26 /* RealmAnalytics.mm */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ - 85B358B925FF8070003A02D4 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = "include/$(PRODUCT_NAME)"; - dstSubfolderSpec = 16; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; F60690111CA2766F0003FB26 /* Copy Headers */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; @@ -53,35 +30,6 @@ /* Begin PBXFileReference section */ 3F8D968A220CE21400327C8C /* RealmReact.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = RealmReact.xcconfig; sourceTree = ""; }; 85B3572625FEB5B9003A02D4 /* realm-js-ios.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = "realm-js-ios.xcframework"; sourceTree = ""; }; - 85B358BB25FF8070003A02D4 /* libGCDWebServer.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libGCDWebServer.a; sourceTree = BUILT_PRODUCTS_DIR; }; - 85B358CC25FF80CF003A02D4 /* GCDWebServerFunctions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFunctions.h; sourceTree = ""; }; - 85B358CD25FF80CF003A02D4 /* GCDWebServerPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerPrivate.h; sourceTree = ""; }; - 85B358CE25FF80CF003A02D4 /* GCDWebServerResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerResponse.m; sourceTree = ""; }; - 85B358CF25FF80CF003A02D4 /* GCDWebServerConnection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerConnection.h; sourceTree = ""; }; - 85B358D025FF80CF003A02D4 /* GCDWebServer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServer.h; sourceTree = ""; }; - 85B358D125FF80CF003A02D4 /* GCDWebServerRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerRequest.m; sourceTree = ""; }; - 85B358D225FF80CF003A02D4 /* GCDWebServerHTTPStatusCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerHTTPStatusCodes.h; sourceTree = ""; }; - 85B358D325FF80CF003A02D4 /* GCDWebServerResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerResponse.h; sourceTree = ""; }; - 85B358D425FF80CF003A02D4 /* GCDWebServerFunctions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFunctions.m; sourceTree = ""; }; - 85B358D525FF80CF003A02D4 /* GCDWebServer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServer.m; sourceTree = ""; }; - 85B358D625FF80CF003A02D4 /* GCDWebServerConnection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerConnection.m; sourceTree = ""; }; - 85B358D725FF80CF003A02D4 /* GCDWebServerRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerRequest.h; sourceTree = ""; }; - 85B358D925FF80CF003A02D4 /* GCDWebServerFileResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileResponse.h; sourceTree = ""; }; - 85B358DA25FF80CF003A02D4 /* GCDWebServerStreamedResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerStreamedResponse.h; sourceTree = ""; }; - 85B358DB25FF80CF003A02D4 /* GCDWebServerErrorResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerErrorResponse.m; sourceTree = ""; }; - 85B358DC25FF80CF003A02D4 /* GCDWebServerDataResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataResponse.h; sourceTree = ""; }; - 85B358DD25FF80CF003A02D4 /* GCDWebServerFileResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileResponse.m; sourceTree = ""; }; - 85B358DE25FF80CF003A02D4 /* GCDWebServerDataResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataResponse.m; sourceTree = ""; }; - 85B358DF25FF80CF003A02D4 /* GCDWebServerErrorResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerErrorResponse.h; sourceTree = ""; }; - 85B358E025FF80CF003A02D4 /* GCDWebServerStreamedResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerStreamedResponse.m; sourceTree = ""; }; - 85B358E225FF80CF003A02D4 /* GCDWebServerDataRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerDataRequest.h; sourceTree = ""; }; - 85B358E325FF80CF003A02D4 /* GCDWebServerMultiPartFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerMultiPartFormRequest.h; sourceTree = ""; }; - 85B358E425FF80CF003A02D4 /* GCDWebServerFileRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerFileRequest.h; sourceTree = ""; }; - 85B358E525FF80CF003A02D4 /* GCDWebServerURLEncodedFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerURLEncodedFormRequest.m; sourceTree = ""; }; - 85B358E625FF80CF003A02D4 /* GCDWebServerMultiPartFormRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerMultiPartFormRequest.m; sourceTree = ""; }; - 85B358E725FF80CF003A02D4 /* GCDWebServerDataRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerDataRequest.m; sourceTree = ""; }; - 85B358E825FF80CF003A02D4 /* GCDWebServerFileRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GCDWebServerFileRequest.m; sourceTree = ""; }; - 85B358E925FF80CF003A02D4 /* GCDWebServerURLEncodedFormRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GCDWebServerURLEncodedFormRequest.h; sourceTree = ""; }; F60690131CA2766F0003FB26 /* libRealmReact.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRealmReact.a; sourceTree = BUILT_PRODUCTS_DIR; }; F60690161CA2766F0003FB26 /* RealmReact.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RealmReact.h; sourceTree = ""; }; F60690181CA2766F0003FB26 /* RealmReact.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RealmReact.mm; sourceTree = ""; }; @@ -90,18 +38,10 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - 85B358B825FF8070003A02D4 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; 85CA5FE41F1536660038172D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 85B358F925FF80DB003A02D4 /* libGCDWebServer.a in Frameworks */, 85B3572725FEB5B9003A02D4 /* realm-js-ios.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -117,68 +57,6 @@ name = Frameworks; sourceTree = ""; }; - 85B358BC25FF8070003A02D4 /* GCDWebServer */ = { - isa = PBXGroup; - children = ( - 85B358CB25FF80CF003A02D4 /* Core */, - 85B358E125FF80CF003A02D4 /* Requests */, - 85B358D825FF80CF003A02D4 /* Responses */, - ); - path = GCDWebServer; - sourceTree = ""; - }; - 85B358CB25FF80CF003A02D4 /* Core */ = { - isa = PBXGroup; - children = ( - 85B358CC25FF80CF003A02D4 /* GCDWebServerFunctions.h */, - 85B358CD25FF80CF003A02D4 /* GCDWebServerPrivate.h */, - 85B358CE25FF80CF003A02D4 /* GCDWebServerResponse.m */, - 85B358CF25FF80CF003A02D4 /* GCDWebServerConnection.h */, - 85B358D025FF80CF003A02D4 /* GCDWebServer.h */, - 85B358D125FF80CF003A02D4 /* GCDWebServerRequest.m */, - 85B358D225FF80CF003A02D4 /* GCDWebServerHTTPStatusCodes.h */, - 85B358D325FF80CF003A02D4 /* GCDWebServerResponse.h */, - 85B358D425FF80CF003A02D4 /* GCDWebServerFunctions.m */, - 85B358D525FF80CF003A02D4 /* GCDWebServer.m */, - 85B358D625FF80CF003A02D4 /* GCDWebServerConnection.m */, - 85B358D725FF80CF003A02D4 /* GCDWebServerRequest.h */, - ); - name = Core; - path = ../../../vendor/GCDWebServer/GCDWebServer/Core; - sourceTree = ""; - }; - 85B358D825FF80CF003A02D4 /* Responses */ = { - isa = PBXGroup; - children = ( - 85B358D925FF80CF003A02D4 /* GCDWebServerFileResponse.h */, - 85B358DA25FF80CF003A02D4 /* GCDWebServerStreamedResponse.h */, - 85B358DB25FF80CF003A02D4 /* GCDWebServerErrorResponse.m */, - 85B358DC25FF80CF003A02D4 /* GCDWebServerDataResponse.h */, - 85B358DD25FF80CF003A02D4 /* GCDWebServerFileResponse.m */, - 85B358DE25FF80CF003A02D4 /* GCDWebServerDataResponse.m */, - 85B358DF25FF80CF003A02D4 /* GCDWebServerErrorResponse.h */, - 85B358E025FF80CF003A02D4 /* GCDWebServerStreamedResponse.m */, - ); - name = Responses; - path = ../../../vendor/GCDWebServer/GCDWebServer/Responses; - sourceTree = ""; - }; - 85B358E125FF80CF003A02D4 /* Requests */ = { - isa = PBXGroup; - children = ( - 85B358E225FF80CF003A02D4 /* GCDWebServerDataRequest.h */, - 85B358E325FF80CF003A02D4 /* GCDWebServerMultiPartFormRequest.h */, - 85B358E425FF80CF003A02D4 /* GCDWebServerFileRequest.h */, - 85B358E525FF80CF003A02D4 /* GCDWebServerURLEncodedFormRequest.m */, - 85B358E625FF80CF003A02D4 /* GCDWebServerMultiPartFormRequest.m */, - 85B358E725FF80CF003A02D4 /* GCDWebServerDataRequest.m */, - 85B358E825FF80CF003A02D4 /* GCDWebServerFileRequest.m */, - 85B358E925FF80CF003A02D4 /* GCDWebServerURLEncodedFormRequest.h */, - ); - name = Requests; - path = ../../../vendor/GCDWebServer/GCDWebServer/Requests; - sourceTree = ""; - }; F606900A1CA2766F0003FB26 = { isa = PBXGroup; children = ( @@ -193,7 +71,6 @@ isa = PBXGroup; children = ( F60690131CA2766F0003FB26 /* libRealmReact.a */, - 85B358BB25FF8070003A02D4 /* libGCDWebServer.a */, ); name = Products; sourceTree = ""; @@ -213,7 +90,6 @@ F60690221CA277BE0003FB26 /* Libraries */ = { isa = PBXGroup; children = ( - 85B358BC25FF8070003A02D4 /* GCDWebServer */, ); name = Libraries; sourceTree = ""; @@ -221,23 +97,6 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 85B358BA25FF8070003A02D4 /* GCDWebServer */ = { - isa = PBXNativeTarget; - buildConfigurationList = 85B358C125FF8070003A02D4 /* Build configuration list for PBXNativeTarget "GCDWebServer" */; - buildPhases = ( - 85B358B725FF8070003A02D4 /* Sources */, - 85B358B825FF8070003A02D4 /* Frameworks */, - 85B358B925FF8070003A02D4 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = GCDWebServer; - productName = GCDWebServer; - productReference = 85B358BB25FF8070003A02D4 /* libGCDWebServer.a */; - productType = "com.apple.product-type.library.static"; - }; F60690121CA2766F0003FB26 /* RealmReact */ = { isa = PBXNativeTarget; buildConfigurationList = F606901C1CA2766F0003FB26 /* Build configuration list for PBXNativeTarget "RealmReact" */; @@ -264,9 +123,6 @@ LastUpgradeCheck = 1010; ORGANIZATIONNAME = Realm; TargetAttributes = { - 85B358BA25FF8070003A02D4 = { - CreatedOnToolsVersion = 12.4; - }; F60690121CA2766F0003FB26 = { CreatedOnToolsVersion = 7.3; }; @@ -286,32 +142,11 @@ projectRoot = ""; targets = ( F60690121CA2766F0003FB26 /* RealmReact */, - 85B358BA25FF8070003A02D4 /* GCDWebServer */, ); }; /* End PBXProject section */ /* Begin PBXSourcesBuildPhase section */ - 85B358B725FF8070003A02D4 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 85B358F425FF80CF003A02D4 /* GCDWebServerMultiPartFormRequest.m in Sources */, - 85B358EF25FF80CF003A02D4 /* GCDWebServerErrorResponse.m in Sources */, - 85B358F525FF80CF003A02D4 /* GCDWebServerDataRequest.m in Sources */, - 85B358F125FF80CF003A02D4 /* GCDWebServerDataResponse.m in Sources */, - 85B358ED25FF80CF003A02D4 /* GCDWebServer.m in Sources */, - 85B358EB25FF80CF003A02D4 /* GCDWebServerRequest.m in Sources */, - 85B358F025FF80CF003A02D4 /* GCDWebServerFileResponse.m in Sources */, - 85B358EE25FF80CF003A02D4 /* GCDWebServerConnection.m in Sources */, - 85B358F225FF80CF003A02D4 /* GCDWebServerStreamedResponse.m in Sources */, - 85B358F325FF80CF003A02D4 /* GCDWebServerURLEncodedFormRequest.m in Sources */, - 85B358EA25FF80CF003A02D4 /* GCDWebServerResponse.m in Sources */, - 85B358EC25FF80CF003A02D4 /* GCDWebServerFunctions.m in Sources */, - 85B358F625FF80CF003A02D4 /* GCDWebServerFileRequest.m in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; F606900F1CA2766F0003FB26 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -324,45 +159,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin XCBuildConfiguration section */ - 85B358C225FF8070003A02D4 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - GCC_C_LANGUAGE_STANDARD = gnu11; - GCC_DYNAMIC_NO_PIC = NO; - MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; - MTL_FAST_MATH = YES; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Debug; - }; - 85B358C325FF8070003A02D4 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; - CLANG_ENABLE_OBJC_WEAK = YES; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; - CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_STYLE = Automatic; - ENABLE_NS_ASSERTIONS = NO; - GCC_C_LANGUAGE_STANDARD = gnu11; - MTL_FAST_MATH = YES; - OTHER_LDFLAGS = "-ObjC"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - }; - name = Release; - }; F606901A1CA2766F0003FB26 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 3F8D968A220CE21400327C8C /* RealmReact.xcconfig */; @@ -395,7 +191,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 10.21.0; + CURRENT_PROJECT_VERSION = 11.0.0; CXX = "$(SRCROOT)/../../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -459,7 +255,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 10.21.0; + CURRENT_PROJECT_VERSION = 11.0.0; CXX = "$(SRCROOT)/../../scripts/ccache-clang++.sh"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -526,15 +322,6 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - 85B358C125FF8070003A02D4 /* Build configuration list for PBXNativeTarget "GCDWebServer" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 85B358C225FF8070003A02D4 /* Debug */, - 85B358C325FF8070003A02D4 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; F606900E1CA2766F0003FB26 /* Build configuration list for PBXProject "RealmReact" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/react-native/ios/RealmReact/RealmReact.h b/react-native/ios/RealmReact/RealmReact.h index b6282be1b9..008c45dcf2 100644 --- a/react-native/ios/RealmReact/RealmReact.h +++ b/react-native/ios/RealmReact/RealmReact.h @@ -17,7 +17,6 @@ //////////////////////////////////////////////////////////////////////////// #import -#import #ifdef __cplusplus extern "C" { @@ -25,8 +24,6 @@ extern "C" { typedef void (^RealmReactEventHandler)(id message); -JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool create); - @interface RealmReact : NSObject - (void)addListenerForEvent:(NSString *)eventName handler:(RealmReactEventHandler)handler; diff --git a/react-native/ios/RealmReact/RealmReact.mm b/react-native/ios/RealmReact/RealmReact.mm index ca6837cd0d..f5629e02e6 100644 --- a/react-native/ios/RealmReact/RealmReact.mm +++ b/react-native/ios/RealmReact/RealmReact.mm @@ -18,84 +18,34 @@ #import "RealmReact.h" -#import +#import #import -#import #import -#include +#import +#import -#import #import #import -#import #import - -#import - -#if DEBUG -#include -#import "GCDWebServer.h" -#import "GCDWebServerDataRequest.h" -#import "GCDWebServerDataResponse.h" -#import "GCDWebServerErrorResponse.h" - -#define WEB_SERVER_PORT 8083 - -using namespace realm::rpc; -#endif - -@interface NSObject () -- (instancetype)initWithJSContext:(JSContext *)context; -- (instancetype)initWithJSContext:(JSContext *)context onThread:(NSThread *)thread; -- (JSContext *)context; -@end +#import +#import // the part of the RCTCxxBridge private class we care about @interface RCTBridge (Realm_RCTCxxBridge) -- (JSGlobalContextRef)jsContextRef; - (void *)runtime; // Expose the CallInvoker so that we can call `invokeAsync` - (std::shared_ptr)jsCallInvoker; @end -extern "C" JSGlobalContextRef RealmReactGetJSGlobalContextForExecutor(id executor, bool create) { - Ivar contextIvar = class_getInstanceVariable([executor class], "_context"); - if (!contextIvar) { - return NULL; - } - - id rctJSContext = object_getIvar(executor, contextIvar); - if (!rctJSContext && create) { - Class RCTJavaScriptContext = NSClassFromString(@"RCTJavaScriptContext"); - if ([RCTJavaScriptContext instancesRespondToSelector:@selector(initWithJSContext:onThread:)]) { - // for RN 0.28.0+ - rctJSContext = [[RCTJavaScriptContext alloc] initWithJSContext:[JSContext new] onThread:[NSThread currentThread]]; - } - else { - // for RN < 0.28.0 - NSCAssert([RCTJavaScriptContext instancesRespondToSelector:@selector(initWithJSContext:)], @"React Native version too old"); - rctJSContext = [[RCTJavaScriptContext alloc] initWithJSContext:[JSContext new]]; - } - - object_setIvar(executor, contextIvar, rctJSContext); - } - - return [rctJSContext context].JSGlobalContextRef; -} - @interface RealmReact () @end @implementation RealmReact { - NSMutableDictionary *_eventHandlers; - // Keep track of whether we are already waiting for the React Native UI queue to be flushed asynchronously - bool waitingForUiFlush; - -#if DEBUG - GCDWebServer *_webServer; - std::unique_ptr _rpcServer; -#endif + NSMutableDictionary *_eventHandlers; + // Keep track of whether we are already waiting for the React Native UI queue + // to be flushed asynchronously + bool waitingForUiFlush; } @synthesize bridge = _bridge; @@ -103,283 +53,131 @@ @implementation RealmReact { RCT_EXPORT_MODULE(Realm) + (BOOL)requiresMainQueueSetup { - return YES; + return YES; } + (void)initialize { - if (self != [RealmReact class]) { - return; - } + if (self != [RealmReact class]) { + return; + } } - (instancetype)init { - self = [super init]; - if (self) { - _eventHandlers = [[NSMutableDictionary alloc] init]; - waitingForUiFlush = false; - } - return self; + self = [super init]; + if (self) { + _eventHandlers = [[NSMutableDictionary alloc] init]; + waitingForUiFlush = false; + } + return self; } - (dispatch_queue_t)methodQueue { - return dispatch_get_main_queue(); + return dispatch_get_main_queue(); } - (NSDictionary *)constantsToExport { -#if DEBUG -#if TARGET_IPHONE_SIMULATOR - NSArray *hosts = @[@"localhost"]; -#else - NSArray *hosts = [self getIPAddresses]; -#endif - - return @{ - @"debugHosts": hosts, - @"debugPort": @(WEB_SERVER_PORT), - }; -#else - return @{}; -#endif -} - -- (void)addListenerForEvent:(NSString *)eventName handler:(RealmReactEventHandler)handler { - NSMutableOrderedSet *handlers = _eventHandlers[eventName]; - if (!handlers) { - handlers = _eventHandlers[eventName] = [[NSMutableOrderedSet alloc] init]; - } - [handlers addObject:handler]; -} - -- (void)removeListenerForEvent:(NSString *)eventName handler:(RealmReactEventHandler)handler { - NSMutableOrderedSet *handlers = _eventHandlers[eventName]; - [handlers removeObject:handler]; -} - -RCT_REMAP_METHOD(emit, emitEvent:(NSString *)eventName withObject:(id)object) { - for (RealmReactEventHandler handler in [_eventHandlers[eventName] copy]) { - handler(object); - } + return @{}; } -#if DEBUG -- (NSArray *)getIPAddresses { - static const char * const wifiInterface = "en0"; - - struct ifaddrs *ifaddrs; - if (getifaddrs(&ifaddrs)) { - NSLog(@"Failed to get interface addresses: %s", strerror(errno)); - return @[]; - } - - NSMutableArray *ipAddresses = [[NSMutableArray alloc] init]; - char host[INET6_ADDRSTRLEN]; - - for (struct ifaddrs *ifaddr = ifaddrs; ifaddr; ifaddr = ifaddr->ifa_next) { - if ((ifaddr->ifa_flags & IFF_LOOPBACK) || !(ifaddr->ifa_flags & IFF_UP)) { - // Ignore loopbacks and interfaces that aren't up. - continue; - } - - struct sockaddr *addr = ifaddr->ifa_addr; - if (addr->sa_family == AF_INET) { - // Ignore link-local ipv4 addresses. - in_addr_t sin_addr = ((struct sockaddr_in *)addr)->sin_addr.s_addr; - if (IN_LOOPBACK(sin_addr) || IN_LINKLOCAL(sin_addr) || IN_ZERONET(sin_addr)) { - continue; - } - } - else if (addr->sa_family == AF_INET6) { - // Ignore link-local ipv6 addresses. - struct in6_addr *sin6_addr = &((struct sockaddr_in6 *)addr)->sin6_addr; - if (IN6_IS_ADDR_LOOPBACK(sin6_addr) || IN6_IS_ADDR_LINKLOCAL(sin6_addr) || IN6_IS_ADDR_UNSPECIFIED(sin6_addr)) { - continue; - } - } - else { - // Ignore addresses that are not ipv4 or ipv6. - continue; - } - - if (strcmp(ifaddr->ifa_name, wifiInterface)) { - // Ignore non-wifi addresses. - continue; - } - if (int error = getnameinfo(addr, addr->sa_len, host, sizeof(host), NULL, 0, NI_NUMERICHOST)) { - NSLog(@"Couldn't resolve host name for address: %s", gai_strerror(error)); - continue; - } - - [ipAddresses addObject:@(host)]; - } - - freeifaddrs(ifaddrs); - return [ipAddresses copy]; +- (void)addListenerForEvent:(NSString *)eventName + handler:(RealmReactEventHandler)handler { + NSMutableOrderedSet *handlers = _eventHandlers[eventName]; + if (!handlers) { + handlers = _eventHandlers[eventName] = [[NSMutableOrderedSet alloc] init]; + } + [handlers addObject:handler]; } -- (void)startRPC { - [GCDWebServer setLogLevel:3]; - _webServer = [[GCDWebServer alloc] init]; - _rpcServer = std::make_unique(); - __weak __typeof__(self) weakSelf = self; - - // Add a handler to respond to POST requests on any URL - [_webServer addDefaultHandlerForMethod:@"POST" - requestClass:[GCDWebServerDataRequest class] - processBlock:^GCDWebServerResponse *(GCDWebServerRequest* request) { - __typeof__(self) self = weakSelf; - RPCServer *rpcServer = self ? self->_rpcServer.get() : nullptr; - GCDWebServerResponse *response; - - try { - NSData *responseData; - - if (rpcServer) { - std::string args = [[(GCDWebServerDataRequest *)request text] UTF8String]; - std::string responseText = rpcServer->perform_request(request.path.UTF8String, args); - - responseData = [NSData dataWithBytes:responseText.c_str() length:responseText.length()]; - } - else { - // we have been deallocated - responseData = [NSData data]; - } - - response = [[GCDWebServerDataResponse alloc] initWithData:responseData contentType:@"application/json"]; - } - catch(std::exception &ex) { - NSLog(@"Invalid RPC request - %@", [(GCDWebServerDataRequest *)request text]); - response = [GCDWebServerErrorResponse responseWithClientError:kGCDWebServerHTTPStatusCode_UnprocessableEntity - underlyingError:nil - message:@"Invalid RPC request"]; - } - - [response setValue:@"http://localhost:8081" forAdditionalHeader:@"Access-Control-Allow-Origin"]; - return response; - }]; - - [_webServer startWithPort:WEB_SERVER_PORT bonjourName:nil]; - return; +- (void)removeListenerForEvent:(NSString *)eventName + handler:(RealmReactEventHandler)handler { + NSMutableOrderedSet *handlers = _eventHandlers[eventName]; + [handlers removeObject:handler]; } -- (void)shutdownRPC { - [_webServer stop]; - [_webServer removeAllHandlers]; - _webServer = nil; - _rpcServer.reset(); +RCT_REMAP_METHOD(emit, emitEvent + : (NSString *)eventName withObject + : (id)object) { + for (RealmReactEventHandler handler in [_eventHandlers[eventName] copy]) { + handler(object); + } } -#endif - (void)invalidate { #if DEBUG - // Immediately close any open sync sessions to prevent race condition with new JS thread - // when hot reloading - RJSCloseSyncSessions(); + // Immediately close any open sync sessions to prevent race condition with new + // JS thread when hot reloading + realm_jsi_close_sync_sessions(); #endif - RJSInvalidateCaches(); - -#if DEBUG - // shutdown rpc if in chrome debug mode - [self shutdownRPC]; -#endif + realm_jsi_invalidate_caches(); } - (void)dealloc { - [self performSelectorOnMainThread:@selector(invalidate) withObject:nil waitUntilDone:YES]; -} - -typedef JSGlobalContextRef (^JSContextRefExtractor)(); - -void _initializeOnJSThread(JSContextRefExtractor jsContextExtractor, std::function flushUiQueueFn) { - // Make sure the previous JS thread is completely finished before continuing. - static __weak NSThread *s_currentJSThread; - while (s_currentJSThread && !s_currentJSThread.finished) { - [NSThread sleepForTimeInterval:0.1]; - } - s_currentJSThread = [NSThread currentThread]; - - RJSInitializeInContext(jsContextExtractor(), flushUiQueueFn); + [self performSelectorOnMainThread:@selector(invalidate) + withObject:nil + waitUntilDone:YES]; } - (void)setBridge:(RCTBridge *)bridge { - _bridge = bridge; - - if (objc_lookUpClass("RCTWebSocketExecutor") && [bridge executorClass] == objc_lookUpClass("RCTWebSocketExecutor")) { -#if DEBUG - [self startRPC]; -#else - @throw [NSException exceptionWithName:@"Invalid Executor" reason:@"Chrome debug mode not supported in Release builds" userInfo:nil]; + _bridge = bridge; + + if (objc_lookUpClass("RCTWebSocketExecutor") && + [bridge executorClass] == objc_lookUpClass("RCTWebSocketExecutor")) { + // Skip native initialization when in legacy Chrome debugging mode +#if !DEBUG + @throw [NSException + exceptionWithName:@"Invalid Executor" + reason:@"Chrome debug mode not supported in Release builds" + userInfo:nil]; #endif - } else if ([bridge isKindOfClass:objc_lookUpClass("RCTCxxBridge")] || [NSStringFromClass([bridge class]) isEqual: @"RCTCxxBridge"]) { - // probe for the new C++ bridge in React Native 0.45+ - - __weak __typeof__(self) weakSelf = self; - __weak __typeof__(bridge) weakBridge = bridge; - - [bridge dispatchBlock:^{ - __typeof__(self) self = weakSelf; - __typeof__(bridge) bridge = weakBridge; - if (!self || !bridge) { - return; - } - - _initializeOnJSThread(^{ - // RN < 0.58 has a private method that returns the js context - if ([bridge respondsToSelector:@selector(jsContextRef)]) { - return [bridge jsContextRef]; - } - // RN 0.58+ wraps the js context in the jsi abstraction layer, - // which doesn't have any way to obtain the JSGlobalContextRef, - // so engage in some undefined behavior and slurp out the - // member variable - struct RealmJSCRuntime { - virtual ~RealmJSCRuntime() = 0; - JSGlobalContextRef ctx_; - }; - return static_cast(bridge.runtime)->ctx_; - }, ^{ - // Calling jsCallInvokver->invokeAsync tells React Native to execute the lambda passed - // in on the JS thread, and then flush the internal "microtask queue", which has the - // effect of flushing any pending UI updates. - // - // We call this after we have called into JS from C++, in order to ensure that the RN - // UI updates in response to any changes from Realm. We need to do this as we bypass - // the usual RN bridge mechanism for communicating between C++ and JS, so without doing - // this RN has no way to know that a change has occurred which might require an update - // (see #4389, facebook/react-native#33006). - // - // Calls are debounced using the waitingForUiFlush flag, so if an async flush is already - // pending when another JS to C++ call happens, we don't call invokeAsync again. This works - // because the work is performed before the microtask queue is flushed - see sequence - // diagram at https://bit.ly/3kexhHm. It might be possible to further optimize this, - // e.g. only flush the queue a maximum of once per frame, but this seems reasonable. - if (!waitingForUiFlush) { - waitingForUiFlush = true; - [bridge jsCallInvoker]->invokeAsync([&](){ - waitingForUiFlush = false; - }); - } - }); - } queue:RCTJSThread]; - } else { // React Native 0.44 and older - id executor = [bridge valueForKey:@"javaScriptExecutor"]; - __weak __typeof__(self) weakSelf = self; - __weak __typeof__(executor) weakExecutor = executor; - - [executor executeBlockOnJavaScriptQueue:^{ - __typeof__(self) self = weakSelf; - __typeof__(executor) executor = weakExecutor; - if (!self || !executor) { - return; + } else if ([bridge isKindOfClass:objc_lookUpClass("RCTCxxBridge")] || + [NSStringFromClass([bridge class]) isEqual:@"RCTCxxBridge"]) { + __weak __typeof__(self) weakSelf = self; + __weak __typeof__(bridge) weakBridge = bridge; + + [bridge + dispatchBlock:^{ + __typeof__(self) self = weakSelf; + __typeof__(bridge) bridge = weakBridge; + if (!self || !bridge) { + return; + } + + // Make sure the previous JS thread is completely finished before + // continuing. + static __weak NSThread *s_currentJSThread; + while (s_currentJSThread && !s_currentJSThread.finished) { + [NSThread sleepForTimeInterval:0.1]; + } + s_currentJSThread = [NSThread currentThread]; + + auto &rt = *static_cast(bridge.runtime); + auto exports = jsi::Object(rt); + realm_jsi_init(rt, exports, ^{ + // Calling jsCallInvokver->invokeAsync tells React Native to execute the lambda passed + // in on the JS thread, and then flush the internal "microtask queue", which has the + // effect of flushing any pending UI updates. + // + // We call this after we have called into JS from C++, in order to ensure that the RN + // UI updates in response to any changes from Realm. We need to do this as we bypass + // the usual RN bridge mechanism for communicating between C++ and JS, so without doing + // this RN has no way to know that a change has occurred which might require an update + // (see #4389, facebook/react-native#33006). + // + // Calls are debounced using the waitingForUiFlush flag, so if an async flush is already + // pending when another JS to C++ call happens, we don't call invokeAsync again. This works + // because the work is performed before the microtask queue is flushed - see sequence + // diagram at https://bit.ly/3kexhHm. It might be possible to further optimize this, + // e.g. only flush the queue a maximum of once per frame, but this seems reasonable. + if (!waitingForUiFlush) { + waitingForUiFlush = true; + [bridge jsCallInvoker]->invokeAsync( + [&]() { waitingForUiFlush = false; }); } - - _initializeOnJSThread(^ { - return RealmReactGetJSGlobalContextForExecutor(executor, true); - }, [&]() { - // jsCallInvoker does not exist on older RN - }); - }]; - } + }); + } + queue:RCTJSThread]; + } } @end diff --git a/scripts/build-android.js b/scripts/build-android.js index 0a8bb47623..c44d2d51d9 100644 --- a/scripts/build-android.js +++ b/scripts/build-android.js @@ -28,8 +28,6 @@ if (!fs.existsSync(rnDir)) { throw new Error("This script needs to be run at the root dir of the project"); } -const copyOutputPath = path.resolve(process.cwd(), "react-native", "android", "src", "main", "jniLibs"); - const buildTypes = ["Debug", "Release", "RelWithDebInfo", "MinSizeRel"]; let architectures = ["x86", "armeabi-v7a", "arm64-v8a", "x86_64"]; const optionDefinitions = [ @@ -60,7 +58,7 @@ const sdkPath = getAndroidSdkPath(); const cmakePath = getCmakePath(sdkPath); const cmakeVersion = getCmakeVersion(sdkPath); -const buildPath = path.resolve(process.cwd(), "build-realm-android"); +const buildPath = path.resolve(process.cwd(), "build-android"); if (options.clean) { if (fs.existsSync(buildPath)) { fs.removeSync(buildPath); @@ -69,9 +67,6 @@ if (options.clean) { fs.ensureDirSync(buildPath, { recursive: true }); -//shared root dir to download jsc once for all architectures -const jscDir = path.resolve(buildPath, "jsc-android"); - for (const arch of architectures) { console.log(`\nBuilding Realm JS Android for ${arch} (${buildType})`); console.log("======================================="); @@ -91,8 +86,7 @@ for (const arch of architectures) { "-DANDROID_TOOLCHAIN=clang", "-DANDROID_NATIVE_API_LEVEL=16", `-DCMAKE_BUILD_TYPE=${buildType}`, - "-DANDROID_STL=c++_static", - `-DJSC_ROOT_DIR=${jscDir}`, + "-DANDROID_STL=c++_shared", process.cwd(), ]; exec(cmakePath, args, { cwd: archBuildDir, stdio: "inherit" }); @@ -100,8 +94,6 @@ for (const arch of architectures) { //cwd is the archBuildDir here, hence build the current dir with "--build ." args = ["--build", "."]; exec(cmakePath, args, { cwd: archBuildDir, stdio: "inherit" }); - - copyOutput(arch, archBuildDir); } generateVersionFile(); @@ -147,22 +139,6 @@ function getVersion() { return version; } -function copyOutput(arch, buildDir) { - const outFile = path.resolve(buildDir, "src", "android", "libs", arch, "librealm.so"); - if (!fs.existsSync(outFile)) { - throw new Error(`Build output file not found: ${outFile}`); - } - - const archDir = path.resolve(copyOutputPath, arch); - if (!fs.existsSync(archDir)) { - fs.mkdirSync(archDir, { recursive: true }); - } - - const targetFile = path.resolve(archDir, "librealm.so"); - console.log(`Copying build file \n${outFile} to \n${targetFile}`); - fs.copyFileSync(outFile, targetFile); -} - function getAndroidSdkPath() { if ("ANDROID_SDK_ROOT" in process.env) { console.log("Using ANDROID_SDK_ROOT env variable"); diff --git a/scripts/build-ios.sh b/scripts/build-ios.sh index 20303d8dfe..57a8d9572e 100755 --- a/scripts/build-ios.sh +++ b/scripts/build-ios.sh @@ -56,6 +56,7 @@ fi DESTINATIONS=() LIBRARIES=() BUILD_LIB_CMDS=() + for platform in "${PLATFORMS[@]}"; do case "$platform" in ios) @@ -105,8 +106,9 @@ for cmd in "${BUILD_LIB_CMDS[@]}"; do eval "${cmd}" done +rm -rf _include mkdir -p _include/realm-js-ios -cp "$PROJECT_ROOT"/src/jsc/{jsc_init.h,rpc.hpp} _include/realm-js-ios/ +cp "$PROJECT_ROOT"/src/jsi/jsi_init.h _include/realm-js-ios/ rm -rf ../realm-js-ios.xcframework xcodebuild -create-xcframework \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c9048f88dd..7bd329353d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,9 +8,9 @@ target_include_directories(realm-js-shared PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) if(DEFINED CMAKE_JS_VERSION) add_subdirectory(node) elseif(ANDROID) - add_subdirectory(jsc) + add_subdirectory(jsi) add_subdirectory(android) elseif(CMAKE_SYSTEM_NAME STREQUAL iOS) - add_subdirectory(jsc) + add_subdirectory(jsi) add_subdirectory(ios) endif() diff --git a/src/android/CMakeLists.txt b/src/android/CMakeLists.txt index 802321782b..e407eb587c 100644 --- a/src/android/CMakeLists.txt +++ b/src/android/CMakeLists.txt @@ -1,61 +1,39 @@ +cmake_minimum_required(VERSION 3.18.1) + +set(REACT_NATIVE_ROOT_DIR "${PACKAGE_ROOT_DIR}/node_modules/react-native") +set(REACT_NATIVE_AAR_DIR "${CMAKE_BINARY_DIR}/../react-native-aar") # TODO: Could we use CMAKE_BINARY_DIR instead? + +set(JSI_HEADER_DIR "${REACT_NATIVE_ROOT_DIR}/ReactCommon/jsi") + add_library(realm-js-android SHARED # $ hack.cpp platform.cpp jni_utils.cpp io_realm_react_RealmReactModule.cpp - jsc_override.cpp ../../node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/turbomodule/core/jni/ReactCommon/CallInvokerHolder.cpp ) set_target_properties(realm-js-android PROPERTIES OUTPUT_NAME "realm" - PREFIX "lib" + PREFIX "lib" SUFFIX ".so" + LIBRARY_OUTPUT_DIRECTORY "${PACKAGE_ROOT_DIR}/react-native/android/src/main/jniLibs/${ANDROID_ABI}" ) -set_target_properties(realm-js-android - PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "libs/${ANDROID_ABI}") - target_compile_definitions(realm-js-android PRIVATE REALM_HAVE_UV=0 REALM_ANDROID=1 REALM_HAVE_CONFIG=1 ) -# JSC_ROOT_DIR allows sharing the same directory for all architectures and skip re-downloading -if(NOT JSC_ROOT_DIR) - # set JSC_ROOT_DIR to current dir - set(JSC_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}") -endif() - -set(JSC_LIB_DIR "${JSC_ROOT_DIR}/package/dist/org/webkit/android-jsc/r241213/jni/${ANDROID_ABI}") -set(JSC_LIB_FILE "${JSC_LIB_DIR}/libjsc.so") +# Extract .so files from the React Native AAR +file(GLOB_RECURSE REACT_NATIVE_AAR_FILE ${REACT_NATIVE_ROOT_DIR}/android/*/react-native-*-release.aar) +file(ARCHIVE_EXTRACT INPUT ${REACT_NATIVE_AAR_FILE} DESTINATION ${REACT_NATIVE_AAR_DIR}) +set(REACT_NATIVE_SO_DIR "${REACT_NATIVE_AAR_DIR}/jni/${ANDROID_ABI}") -if(NOT EXISTS ${JSC_LIB_FILE}) - set(JSC_LIB_URL "https://registry.npmjs.org/jsc-android/-/jsc-android-241213.1.0.tgz") - - message(STATUS "Getting ${JSC_LIB_URL}...") - file(DOWNLOAD "${JSC_LIB_URL}" "${JSC_ROOT_DIR}/jsc-android-241213.1.0.tgz" STATUS download_status) - - list(GET download_status 0 status_code) - if (NOT "${status_code}" STREQUAL "0") - message(FATAL_ERROR "Downloading ${url}... Failed. Status: ${download_status}") - endif() - - message(STATUS "Uncompressing ${JSC_ROOT_DIR}/jsc-android-241213.1.0.tgz") - execute_process( - COMMAND ${CMAKE_COMMAND} -E tar xfz "jsc-android-241213.1.0.tgz" - WORKING_DIRECTORY "${JSC_ROOT_DIR}" - ) - - message(STATUS "Uncompressing ${JSC_ROOT_DIR}/package/dist/org/webkit/android-jsc/r241213/android-jsc-r241213.aar") - execute_process( - COMMAND ${CMAKE_COMMAND} -E tar xfz "android-jsc-r241213.aar" - WORKING_DIRECTORY "${JSC_ROOT_DIR}/package/dist/org/webkit/android-jsc/r241213/" - ) -endif() +target_link_directories(realm-js-android PRIVATE ${REACT_NATIVE_SO_DIR}) +target_include_directories(realm-js-android PRIVATE ${JSI_HEADER_DIR}) # FBJNI_ROOT_DIR allows sharing the same directory for all architectures and skip re-downloading if(NOT FBJNI_ROOT_DIR) @@ -92,8 +70,6 @@ else() target_compile_definitions(realm-js-android PUBLIC REALM_WRAP_MEMMOVE=0) endif() -target_link_options(realm-js-android PUBLIC -fvisibility=hidden) - if(REALM_JS_BUILD_CORE_FROM_SOURCE AND TARGET ObjectStore) target_compile_definitions(ObjectStore PUBLIC REALM_PLATFORM=Android @@ -101,13 +77,6 @@ if(REALM_JS_BUILD_CORE_FROM_SOURCE AND TARGET ObjectStore) ) endif() -# Setup JSC library -target_include_directories(realm-js-jsc PUBLIC "../jsc/include") -target_include_directories(realm-js-android PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -target_link_directories(realm-js-android PUBLIC "${JSC_LIB_DIR}") -target_link_libraries(realm-js-android realm-js-jsc realm-js-shared "${JSC_LIB_FILE}") - # Setup fbjni library find_library(FBJNI_LIBRARY fbjni PATHS ${FBJNI_LIB_DIR} NO_CMAKE_FIND_ROOT_PATH) target_include_directories(realm-js-android PUBLIC @@ -116,7 +85,12 @@ target_include_directories(realm-js-android PUBLIC ../../node_modules/react-native/ReactCommon/callinvoker/ ) -target_link_libraries(realm-js-android ${FBJNI_LIBRARY} realm-js-shared) +target_link_libraries(realm-js-android + ${FBJNI_LIBRARY} + realm-js-jsi + realm-js-shared + jsi +) if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel") add_custom_command(TARGET realm-js-android diff --git a/src/android/io_realm_react_RealmReactModule.cpp b/src/android/io_realm_react_RealmReactModule.cpp index a839e67b52..cd9496d64e 100644 --- a/src/android/io_realm_react_RealmReactModule.cpp +++ b/src/android/io_realm_react_RealmReactModule.cpp @@ -21,19 +21,20 @@ #include #include #include +#include -#include "io_realm_react_RealmReactModule.h" -#include "rpc.hpp" +#include +#include #include "platform.hpp" #include "jni_utils.hpp" -#include "jsc_externs.hpp" #include "hack.hpp" -using namespace realm::rpc; +#include "io_realm_react_RealmReactModule.h" + +namespace jsi = facebook::jsi; + using namespace realm::jni_util; -static RPCServer* s_rpc_server; -extern bool realmContextInjected; jclass ssl_helper_class; namespace realm { @@ -99,42 +100,22 @@ JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_setDefaultRealmFileD realm::default_realm_file_directory().c_str()); } -JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactModule_setupChromeDebugModeRealmJsContext(JNIEnv*, jobject) +JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_install(JNIEnv*, jobject, jlong runtimePointer) { - __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "setupChromeDebugModeRealmJsContext"); - if (s_rpc_server) { - delete s_rpc_server; + __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "install"); + auto runtime = reinterpret_cast(runtimePointer); + if (runtime) { + __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "Building an exports object"); + auto exports = jsi::Object(*runtime); + __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "Initializing ..."); + realm_jsi_init(*runtime, exports, [] {}); } - s_rpc_server = new RPCServer(); - return (jlong)s_rpc_server; -} - -JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactModule_processChromeDebugCommand(JNIEnv* env, jobject, - jstring chrome_cmd, - jstring chrome_args) -{ - const char* cmd = env->GetStringUTFChars(chrome_cmd, NULL); - const char* args = env->GetStringUTFChars(chrome_args, NULL); - std::string response = s_rpc_server->perform_request(cmd, args); - env->ReleaseStringUTFChars(chrome_cmd, cmd); - env->ReleaseStringUTFChars(chrome_args, args); - return env->NewStringUTF(response.c_str()); -} - -JNIEXPORT jboolean JNICALL Java_io_realm_react_RealmReactModule_tryRunTask(JNIEnv* env, jobject) -{ - jboolean result = s_rpc_server->try_run_task(); - return result; -} - -JNIEXPORT jboolean JNICALL Java_io_realm_react_RealmReactModule_isContextInjected(JNIEnv* env, jobject) -{ - return realmContextInjected; } -JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_clearContextInjectedFlag(JNIEnv* env, jobject) +JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_invalidateCaches(JNIEnv*, jobject) { - realmContextInjected = false; + __android_log_print(ANDROID_LOG_VERBOSE, "JSRealm", "invalidateCaches"); + realm_jsi_invalidate_caches(); } // Setup the flush_ui_queue function we use to flush the React Native UI queue whenever we call from C++ to JS. diff --git a/src/android/io_realm_react_RealmReactModule.h b/src/android/io_realm_react_RealmReactModule.h index 6a6008b71b..d886958ebc 100644 --- a/src/android/io_realm_react_RealmReactModule.h +++ b/src/android/io_realm_react_RealmReactModule.h @@ -7,22 +7,6 @@ #ifdef __cplusplus extern "C" { #endif -#undef io_realm_react_RealmReactModule_DEFAULT_PORT -#define io_realm_react_RealmReactModule_DEFAULT_PORT 8083L -/* - * Class: io_realm_react_RealmReactModule - * Method: isContextInjected - * Signature: ()Z - */ -JNIEXPORT jboolean JNICALL Java_io_realm_react_RealmReactModule_isContextInjected(JNIEnv*, jobject); - -/* - * Class: io_realm_react_RealmReactModule - * Method: clearContextInjectedFlag - * Signature: ()V - */ -JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_clearContextInjectedFlag(JNIEnv*, jobject); - /* * Class: io_realm_react_RealmReactModule * Method: setDefaultRealmFileDirectory @@ -33,26 +17,20 @@ JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_setDefaultRealmFileD /* * Class: io_realm_react_RealmReactModule - * Method: setupChromeDebugModeRealmJsContext - * Signature: ()J + * Method: install + * Signature: (J)V */ -JNIEXPORT jlong JNICALL Java_io_realm_react_RealmReactModule_setupChromeDebugModeRealmJsContext(JNIEnv*, jobject); +JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_install + (JNIEnv *, jobject, jlong); /* * Class: io_realm_react_RealmReactModule - * Method: processChromeDebugCommand - * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL Java_io_realm_react_RealmReactModule_processChromeDebugCommand(JNIEnv*, jobject, jstring, - jstring); - -/* - * Class: io_realm_react_RealmReactModule - * Method: tryRunTask - * Signature: ()Z + * Method: invalidateCaches + * Signature: ()V */ -JNIEXPORT jboolean JNICALL Java_io_realm_react_RealmReactModule_tryRunTask(JNIEnv*, jobject); - +JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_invalidateCaches + (JNIEnv *, jobject); + /* * Class: io_realm_react_RealmReactModule * Method: setupFlushUiQueue diff --git a/src/android/jsc_override.cpp b/src/android/jsc_override.cpp deleted file mode 100644 index 1b3ae07fcf..0000000000 --- a/src/android/jsc_override.cpp +++ /dev/null @@ -1,214 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include - -#include "jsc_init.h" -#include -#include - -/** -`__attribute__((constructor))` will trigger a first call to swap_function() which will install the function hook. - -The hook function is simply a technique (originally published in 1999 -https://www.microsoft.com/en-us/research/project/detours/#!publications) to replace the original function call by -another one by substituting the address of the original function `JSGlobalContextCreateInGroup` by an assembly JUMP -instruction in order to branch into our custom function `create_context` (which has the same signature as the -original).The custom function will then remove the hook to be able to invoke the original -`JSGlobalContextCreateInGroup` in order to obtain the JS context, needed to initialize Realm. - - The assembly code to perform the jump is architecture specific, similarly for the size of this "Hook". Here's how -it's calculated for the various architectures - - - ARM 32 bit: - ARM supports two instruction mode, Thumb & ARM (with different size for the opcodes). - if we're using Thumb then the jump is performed using the BX instruction (see -https://web.eecs.umich.edu/~prabal/teaching/eecs373-f10/readings/ARMv7-M_ARM.pdf) BX allows to branch to a specific -address stored in a global register with the option to switch instruction from Thumb to ARM. This is performed using -this code LDR R3, [PC, #0]; <---- This load the current address referenced by the current Program Counter address with -a 0 offset BX R3; <---- This will perform the jump - - memcpy(orig_func, "\x00\x4b\x18\x47", 4); - memcpy(orig_func + 4, &new_func, 4); - -For non-Thumb we simply set the current program PC to the address of the new function (swap_function) - LDR PC, [PC] <---- [PC] get the address of the current PC (remember the first call of swap_function is triggered -automatically when loading the shared object) so when execute this assembly later at the address of the original -function, this will jump to swap_function - - memcpy(orig_func, "\x00\xf0\x9f\xe5", 4); - memcpy(orig_func + 4, &new_func, 4); - -- ARM 64 bit: -Doesn't have Thumb instruction, but also doesn't expose the program counter (PC) as a general register so it cannot be -used anymore. The workaround is to use a `BR` instruction (see -https://static.docs.arm.com/ddi0596/a/DDI_0596_ARM_a64_instruction_set_architecture.pdf) (be careful to not use `BLR` -accidentally BLR perform the same thing as BR but as a side effect will set the PC to PC + 4 after the jump, 4 is the -size of the instruction, the idea of BLR is to jump to a subroutine, then go back to the next instruction in the -assembly after the jump completes, the next instruction is located at current PC + 4, this is not our use case since -we want to perform an unconditional jump). LDR X3, .+8 <--- load into the global register X3 the address of the -current PC + an offset of 8 bytes BR X3 <---- perform the jump into the address of the new function (swap_function) -located 8 bytes after the previous two assembly instructions. - - memcpy(orig_func, "\x43\x00\x00\x58\x60\x00\x1F\xD6", 8); - memcpy(orig_func + 8, &new_func, 8); - -How is the assembly transformed into hex code: -- You can use the manual instruction to work out the instruction value based on the opcodes. -- You can also write the assembly then cross-compile to access the hexcode after disassembling it -Example: -create a file `hook.s` with the following assembly content -``` -.section .text -.global _start - -_start: -ldr x3, .+8 -br x3 -```` -you can now cross compile it to AARCH64 on mac using the NDK toolchain -$NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/aarch64-linux-android/bin/as hook.s -o hook.o - -you can link it using -$NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/aarch64-linux-android/bin/ld hook.o -o hook - -now inspect the ARM64 executable using -objdump -d hook -``` -hook: file format ELF64-aarch64-little - -Disassembly of section .text: -_start: - 4000b0: 43 00 00 58 ldr x3, #8 - 4000b4: 60 00 1f d6 br x3 -``` -This is the method used for AMR 64bit - -- You can also use this online tool to convert hex to assembly & assembly to hex http://armconverter.com - - -The ARM_FUNCTION_HOOK_SIZE is the number of bytes the hook need to rewrite to install the jump code. - -Example for AARCH64: - It's simply two ARM64 instructions, 4 bytes each (the first memcpy is 8) and the actual function address will also be -8 bytes (second memcpy) so the total is 16 bytes. -*/ -#if __aarch64__ -#define HOOK_SIZE 16 -#define ARM_FUNCTION_HOOK "\x43\x00\x00\x58\x60\x00\x1F\xD6" -#define ARM_FUNCTION_HOOK_SIZE 8 -#elif defined(__arm__) -#define HOOK_SIZE 8 -#define ARM_FUNCTION_HOOK "\x00\xf0\x9f\xe5" -#define ARM_FUNCTION_HOOK_SIZE 4 -#else -#define HOOK_SIZE 5 -#endif - -bool realmContextInjected; - -static void swap_function() __attribute__((constructor)); - -static JSGlobalContextRef create_context(JSContextGroupRef group, JSClassRef global_class) -{ - static std::mutex s_mutex; - std::lock_guard lock(s_mutex); - - // Replace JSGlobalContextCreateInGroup with its original implementation and call it. - swap_function(); - JSGlobalContextRef ctx = JSGlobalContextCreateInGroup(group, global_class); - - // Reinstall the hook. - swap_function(); - - // Clear cache from previous instances. - RJSInvalidateCaches(); - - // We pass a no-op lambda for the `flushUiQueue` function, as there's no easy way to - // access the React Native context from here. Instead we set the `flushUiQueue` function - // when the RealmReactModule Java module initialises. - RJSInitializeInContext(ctx, []() {}); - realmContextInjected = true; - return ctx; -} - -static void swap_function() -{ - // TODO: should we do a byte alignment for s_orig_code using alignas(16) or similar? - static int8_t s_orig_code[HOOK_SIZE]; - static bool s_swapped = false; - - int8_t* orig_func = (int8_t*)&JSGlobalContextCreateInGroup; - int8_t* new_func = (int8_t*)&create_context; - - bool orig_thumb = false; -#if __arm__ && !defined(__aarch64__) - orig_thumb = (uintptr_t)orig_func % 4 != 0; - if (orig_thumb) { - orig_func--; - } -#endif - - size_t page_size = sysconf(_SC_PAGESIZE); - uintptr_t page_start = (uintptr_t)orig_func & ~(page_size - 1); - uintptr_t code_end = (uintptr_t)orig_func + HOOK_SIZE; - - // Make this memory region writable. - mprotect((void*)page_start, code_end - page_start, PROT_READ | PROT_WRITE | PROT_EXEC); - - if (s_swapped) { - // Copy original code back into place. - memcpy(orig_func, s_orig_code, HOOK_SIZE); - } - else { - // Store the original code before replacing it. - memcpy(s_orig_code, orig_func, HOOK_SIZE); - -#if __arm__ || __aarch64__ - if (orig_thumb) { - // LDR R3, [PC, #0]; BX R3; - memcpy(orig_func, "\x00\x4b\x18\x47", ARM_FUNCTION_HOOK_SIZE); - memcpy(orig_func + ARM_FUNCTION_HOOK_SIZE, &new_func, ARM_FUNCTION_HOOK_SIZE); - } - else { - memcpy(orig_func, ARM_FUNCTION_HOOK, ARM_FUNCTION_HOOK_SIZE); - memcpy(orig_func + ARM_FUNCTION_HOOK_SIZE, &new_func, ARM_FUNCTION_HOOK_SIZE); - } -#else - // TODO: It would be safer to generate an indirect jump to an absolute address since distance might be greater - // than +/- 2^31 - int32_t jmp_offset = (int64_t)new_func - (int64_t)orig_func - HOOK_SIZE; - - // Change original function to jump to our new one. - *orig_func = 0xE9; // JMP - *(int32_t*)(orig_func + 1) = jmp_offset; -#endif - } - - s_swapped = !s_swapped; - - __builtin___clear_cache((char*)page_start, (char*)code_end); - - // Return this region to no longer being writable. - mprotect((void*)page_start, code_end - page_start, PROT_READ | PROT_EXEC); -} diff --git a/src/ios/CMakeLists.txt b/src/ios/CMakeLists.txt index a7d7d5aa8c..3af3dc6136 100644 --- a/src/ios/CMakeLists.txt +++ b/src/ios/CMakeLists.txt @@ -2,5 +2,6 @@ add_library(realm-js-ios STATIC platform.mm ) -target_link_libraries(realm-js-jsc PUBLIC "-framework JavaScriptCore") -target_link_libraries(realm-js-ios realm-js-jsc realm-js-shared) +# target_link_libraries(realm-js-jsc PUBLIC "-framework JavaScriptCore") +# target_link_libraries(realm-js-ios realm-js-jsc realm-js-shared) +target_link_libraries(realm-js-ios realm-js-hermes realm-js-shared) diff --git a/src/js_object_accessor.hpp b/src/js_object_accessor.hpp index f18e20558b..57428c4d71 100644 --- a/src/js_object_accessor.hpp +++ b/src/js_object_accessor.hpp @@ -562,23 +562,21 @@ struct Unbox { auto current_realm = native_accessor->m_realm; auto js_object = Value::validated_to_object(native_accessor->m_ctx, value); - auto realm_object = get_internal>(native_accessor->m_ctx, js_object); - auto is_ros_instance = + auto is_realm_object = Object::template is_instance>(native_accessor->m_ctx, js_object); - if (is_ros_instance && realm_object && realm_object->realm() == current_realm) { - return realm_object->obj(); - } - - if (is_ros_instance && !policy.copy && !policy.update && !policy.create) { - throw std::runtime_error("Realm object is from another Realm"); - } - - // if our RealmObject isn't in ObjectStore, it's a detached object - // (not in to database), and we can't add it - if (is_ros_instance && !realm_object) { - throw std::runtime_error("Cannot reference a detached instance of Realm.Object"); + if (is_realm_object) { + auto realm_object = get_internal>(native_accessor->m_ctx, js_object); + if (realm_object && realm_object->realm() == current_realm) { + return realm_object->obj(); + } + else if (!policy.copy && !policy.update && !policy.create) { + throw std::runtime_error("Realm object is from another Realm"); + } + else if (!realm_object) { + throw std::runtime_error("Cannot reference a detached instance of Realm.Object"); + } } if (!policy.create) { diff --git a/src/js_realm.hpp b/src/js_realm.hpp index 24e6710bd4..731672c836 100644 --- a/src/js_realm.hpp +++ b/src/js_realm.hpp @@ -31,6 +31,7 @@ #include "platform.hpp" #include "realm/binary_data.hpp" #include +#include #if REALM_ENABLE_SYNC #include "js_sync.hpp" @@ -346,6 +347,11 @@ class RealmClass : public ClassDefinition> { static void get_schema_name_from_object(ContextType, ObjectType, Arguments&, ReturnValue&); static void update_schema(ContextType, ObjectType, Arguments&, ReturnValue&); + // NOTE: __to_object and __to_boolean are shims that allow type conversion tests + // on unit tests / CI. They probably shouldn't be available in production + static void __to_object(ContextType, ObjectType, Arguments&, ReturnValue&); + static void __to_boolean(ContextType, ObjectType, Arguments&, ReturnValue&); + #if REALM_ENABLE_SYNC static void async_open_realm(ContextType, ObjectType, Arguments&, ReturnValue&); #endif @@ -431,6 +437,11 @@ class RealmClass : public ClassDefinition> { {"_objectForObjectKey", wrap}, {"_updateSchema", wrap}, {"_schemaName", wrap}, + + // NOTE: __to_object and __to_boolean are shims that allow type conversion tests + // on unit tests / CI. They probably shouldn't be available in production + {"__to_object", wrap<__to_object>}, + {"__to_boolean", wrap<__to_boolean>}, }; PropertyMap const properties = { @@ -534,6 +545,9 @@ class RealmClass : public ClassDefinition> { template inline typename T::Function RealmClass::create_constructor(ContextType ctx) { + // Only calling to populate static cache. Eventually this should be stored somewhere non-static. + (void)ObjectWrap>::create_constructor(ctx); + FunctionType realm_constructor = ObjectWrap>::create_constructor(ctx); FunctionType collection_constructor = ObjectWrap>::create_constructor(ctx); FunctionType list_constructor = ObjectWrap>::create_constructor(ctx); @@ -674,7 +688,26 @@ bool RealmClass::get_realm_config(ContextType ctx, size_t argc, const ValueTy ValueType schema_value = Object::get_property(ctx, object, schema_string); if (!Value::is_undefined(ctx, schema_value)) { ObjectType schema_array = Value::validated_to_array(ctx, schema_value, "schema"); - config.schema.emplace(Schema::parse_schema(ctx, schema_array, defaults, constructors)); + auto schema = Schema::parse_schema(ctx, schema_array, defaults, constructors); + // Check that all constructors provided by the user extend Realm.Object + const auto& realm_constructor = Value::validated_to_object(ctx, Object::get_global(ctx, "Realm")); + const auto& realm_object_constructor = Object::validated_get_object(ctx, realm_constructor, "Object"); + for (const auto& [name, constructor] : constructors) { + const auto& prototype = Value::validated_to_object(ctx, Object::get_prototype(ctx, constructor)); + if (prototype != realm_object_constructor) { + const std::string& class_name = + Object::validated_get_string(ctx, constructor, "name", "Failed to read class name"); + if (class_name == name) { + throw std::invalid_argument( + util::format("Class '%1' must extend Realm.Object", class_name)); + } + else { + throw std::invalid_argument(util::format( + "Class '%1' (declaring '%2' schema) must extend Realm.Object", class_name, name)); + } + } + } + config.schema.emplace(std::move(schema)); schema_updated = true; } @@ -1165,10 +1198,10 @@ void RealmClass::async_open_realm(ContextType ctx, ObjectType this_object, Ar Function::callback(protected_ctx, protected_callback, protected_this, 1, callback_arguments); } - std::shared_ptr task; - task = Realm::get_synchronized_realm(config); + std::shared_ptr task = Realm::get_synchronized_realm(config); - realm::util::EventLoopDispatcher callback_handler([=, defaults = std::move(defaults), + realm::util::EventLoopDispatcher callback_handler([=, args_count = args.count, + defaults = std::move(defaults), constructors = std::move(constructors)]( ThreadSafeReference&& realm_ref, std::exception_ptr error) { @@ -1200,7 +1233,7 @@ void RealmClass::async_open_realm(ContextType ctx, ObjectType this_object, Ar try { ValueType unprotected_args = protected_args; - handle_initial_subscriptions(protected_ctx, args.count - 1, &unprotected_args, realm, realm_exists); + handle_initial_subscriptions(protected_ctx, args_count - 1, &unprotected_args, realm, realm_exists); } catch (TypeErrorException e) { auto error = Object::create_error(protected_ctx, e.what()); @@ -1727,6 +1760,24 @@ void RealmClass::get_schema_name_from_object(ContextType ctx, ObjectType this return_value.set(object_schema.name); } +template +void RealmClass::__to_object(ContextType ctx, ObjectType this_object, Arguments& args, ReturnValue& return_value) +{ + args.validate_count(1); + ObjectType newobj = Value::to_object(ctx, args[0]); + + return_value.set(newobj); +} + +template +void RealmClass::__to_boolean(ContextType ctx, ObjectType this_object, Arguments& args, ReturnValue& return_value) +{ + args.validate_count(1); + bool is_bool = Value::to_boolean(ctx, args[0]); + + return_value.set(is_bool); +} + /** * Updates the schema. * diff --git a/src/js_realm_object.hpp b/src/js_realm_object.hpp index 631302d061..510057af56 100644 --- a/src/js_realm_object.hpp +++ b/src/js_realm_object.hpp @@ -64,6 +64,7 @@ struct RealmObjectClass : ClassDefinition> { using FunctionType = typename T::Function; using ObjectType = typename T::Object; using ValueType = typename T::Value; + using Context = js::Context; using String = js::String; using Value = js::Value; using Object = js::Object; @@ -74,6 +75,7 @@ struct RealmObjectClass : ClassDefinition> { static ObjectType create_instance(ContextType, realm::js::RealmObject); + static void constructor(ContextType, ObjectType, Arguments&); static void get_property(ContextType, ObjectType, const String&, ReturnValue&); static bool set_property(ContextType, ObjectType, const String&, ValueType); static std::vector get_property_names(ContextType, ObjectType); @@ -168,6 +170,44 @@ typename T::Object RealmObjectClass::create_instance(ContextType ctx, realm:: } } +/** + * @brief Implements the constructor for a Realm.Object, calling the `Realm#create` instance method to create an + * object in the database. + * + * @note This differs from `RealmObjectClass::create_instance` as it is executed when end-users construct a `new + * Realm.Object()` (or another user-defined class extending `Realm.Object`), whereas `create_instance` is called when + * reading objects from the database. + * + * @tparam T Engine specific types. + * @param ctx JS context. + * @param this_object JS object being returned to the user once constructed. + * @param args Arguments passed by the user when calling the constructor. + */ +template +void RealmObjectClass::constructor(ContextType ctx, ObjectType this_object, Arguments& args) +{ + // Parse aguments + args.validate_count(2); + auto constructor = Object::validated_get_object(ctx, this_object, "constructor"); + auto realm = Value::validated_to_object(ctx, args[0], "realm"); + auto values = Value::validated_to_object(ctx, args[1], "values"); + + // Create an object + std::vector create_args{constructor, values}; + Arguments create_arguments{ctx, create_args.size(), create_args.data()}; + ReturnValue result{ctx}; + RealmClass::create(ctx, realm, create_arguments, result); + ObjectType tmp_realm_object = Value::validated_to_object(ctx, result); + + // Copy the internal from the constructed object onto this_object + auto realm_object = get_internal>(ctx, tmp_realm_object); + // The finalizer on the ObjectWrap (applied inside of set_internal) will delete the `new_realm_object` which is + // why we create a new instance to avoid a double free (the first of which will happen when the `tmp_realm_object` + // destructs). + auto new_realm_object = new realm::js::RealmObject(*realm_object); + set_internal>(ctx, this_object, new_realm_object); +} + template void RealmObjectClass::get_property(ContextType ctx, ObjectType object, const String& property_name, ReturnValue& return_value) @@ -394,7 +434,7 @@ void RealmObjectClass::add_listener(ContextType ctx, ObjectType this_object, auto callback = Value::validated_to_function(ctx, args[0]); Protected protected_callback(ctx, callback); Protected protected_this(ctx, this_object); - Protected protected_ctx(Context::get_global_context(ctx)); + Protected protected_ctx(Context::get_global_context(ctx)); auto token = realm_object->add_notification_callback([=](CollectionChangeSet const& change_set) { HANDLESCOPE(protected_ctx) diff --git a/src/js_schema.hpp b/src/js_schema.hpp index 361e824466..0a19906763 100644 --- a/src/js_schema.hpp +++ b/src/js_schema.hpp @@ -437,6 +437,7 @@ template typename T::Object Schema::object_for_schema(ContextType ctx, const realm::Schema& schema) { ObjectType object = Object::create_array(ctx); + Object::set_property(ctx, object, "length", Value::from_number(ctx, double(schema.size()))); uint32_t count = 0; for (auto& object_schema : schema) { Object::set_property(ctx, object, count++, object_for_object_schema(ctx, object_schema)); diff --git a/src/js_set.hpp b/src/js_set.hpp index 84605d3228..91209cec26 100644 --- a/src/js_set.hpp +++ b/src/js_set.hpp @@ -446,7 +446,7 @@ void SetClass::filtered(ContextType ctx, ObjectType this_object, Arguments& a template void SetClass::get_type(ContextType ctx, ObjectType object, ReturnValue& return_value) { - auto const set = get_internal>(ctx, object); + auto const set = get_internal>(ctx, object); return_value.set(local_string_for_property_type(set->get_type() & ~realm::PropertyType::Flags)); } diff --git a/src/js_types.hpp b/src/js_types.hpp index e77331b3d1..5f77ae097e 100644 --- a/src/js_types.hpp +++ b/src/js_types.hpp @@ -104,8 +104,37 @@ template struct Context { using ContextType = typename T::Context; using GlobalContextType = typename T::GlobalContext; + using Invalidator = std::function; + + inline static std::vector invalidators{}; static GlobalContextType get_global_context(ContextType); + + /** + * @brief Register a function to be called just before the current context gets destroyed. + * + * Use this function to ensure caches derived from a context gets invalidated and destructed + * while the context is still around. + * + * @param invalidator The function to call just before the context gets destroyed. + */ + static void register_invalidator(Invalidator invalidator) + { + invalidators.emplace_back(invalidator); + } + + /** + * @brief Call all registered invalidator functions. + * + * This clears the list of invalidators, ensuring that they'll only get called once per context. + */ + static void invalidate() + { + for (auto& invalidator : invalidators) { + invalidator(); + } + invalidators.clear(); + } }; class TypeErrorException : public std::invalid_argument { @@ -463,7 +492,7 @@ struct Object { static typename ClassType::Internal* get_internal(ContextType ctx, const ObjectType&); template - static void set_internal(ContextType ctx, const ObjectType&, typename ClassType::Internal*); + static void set_internal(ContextType ctx, ObjectType&, typename ClassType::Internal*); static ObjectType create_from_app_error(ContextType, const app::AppError&); static ValueType create_from_optional_app_error(ContextType, const std::optional&); @@ -535,6 +564,8 @@ struct ReturnValue { void set(uint32_t); void set_null(); void set_undefined(); + + operator ValueType() const; }; template @@ -560,14 +591,35 @@ REALM_JS_INLINE typename T::Object create_instance_by_schema(typename T::Context return Object::template create_instance_by_schema(ctx, schema, internal); } +/** + * @brief Get the internal (C++) object backing a JS object. + * + * @tparam T Engine specific types. + * @tparam ClassType Class implementing the C++ interface backing the JS accessor object (passed as `object`). + * @param ctx JS context. + * @param object JS object with an internal object. + * @return Pointer to the internal object. + */ template REALM_JS_INLINE typename ClassType::Internal* get_internal(typename T::Context ctx, const typename T::Object& object) { return Object::template get_internal(ctx, object); } +/** + * @brief Set the internal (C++) object backing the JS object. + * + * @note Calling this transfer ownership of the object pointed to by `ptr` and links it to the lifetime of to the + * `object` passed as argument. + * + * @tparam T Engine specific types. + * @tparam ClassType Class implementing the C++ interface backing the JS accessor object (passed as `object`). + * @param ctx JS context. + * @param object JS object having its internal set. + * @param ptr A pointer to an internal object. + */ template -REALM_JS_INLINE void set_internal(typename T::Context ctx, const typename T::Object& object, +REALM_JS_INLINE void set_internal(typename T::Context ctx, typename T::Object& object, typename ClassType::Internal* ptr) { Object::template set_internal(ctx, object, ptr); diff --git a/src/jsc/CMakeLists.txt b/src/jsc/CMakeLists.txt deleted file mode 100644 index 656264d45d..0000000000 --- a/src/jsc/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -add_library(realm-js-jsc OBJECT - jsc_init.cpp - jsc_value.cpp - rpc.cpp -) - -target_include_directories(realm-js-jsc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - -target_link_libraries(realm-js-jsc PUBLIC realm-js-shared) diff --git a/src/jsc/include/JavaScriptCore/JSBase.h b/src/jsc/include/JavaScriptCore/JSBase.h deleted file mode 100644 index 7d0ea3a53d..0000000000 --- a/src/jsc/include/JavaScriptCore/JSBase.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (C) 2006 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef JSBase_h -#define JSBase_h - -#ifndef __cplusplus -#include -#endif - -#ifdef __OBJC__ -#import -#endif - -/* JavaScript engine interface */ - -/*! @typedef JSContextGroupRef A group that associates JavaScript contexts with one another. Contexts in the same group may share and exchange JavaScript objects. */ -typedef const struct OpaqueJSContextGroup* JSContextGroupRef; - -/*! @typedef JSContextRef A JavaScript execution context. Holds the global object and other execution state. */ -typedef const struct OpaqueJSContext* JSContextRef; - -/*! @typedef JSGlobalContextRef A global JavaScript execution context. A JSGlobalContext is a JSContext. */ -typedef struct OpaqueJSContext* JSGlobalContextRef; - -/*! @typedef JSStringRef A UTF16 character buffer. The fundamental string representation in JavaScript. */ -typedef struct OpaqueJSString* JSStringRef; - -/*! @typedef JSClassRef A JavaScript class. Used with JSObjectMake to construct objects with custom behavior. */ -typedef struct OpaqueJSClass* JSClassRef; - -/*! @typedef JSPropertyNameArrayRef An array of JavaScript property names. */ -typedef struct OpaqueJSPropertyNameArray* JSPropertyNameArrayRef; - -/*! @typedef JSPropertyNameAccumulatorRef An ordered set used to collect the names of a JavaScript object's properties. */ -typedef struct OpaqueJSPropertyNameAccumulator* JSPropertyNameAccumulatorRef; - - -/* JavaScript data types */ - -/*! @typedef JSValueRef A JavaScript value. The base type for all JavaScript values, and polymorphic functions on them. */ -typedef const struct OpaqueJSValue* JSValueRef; - -/*! @typedef JSObjectRef A JavaScript object. A JSObject is a JSValue. */ -typedef struct OpaqueJSValue* JSObjectRef; - -/* JavaScript symbol exports */ -/* These rules should stay the same as in WebKit2/Shared/API/c/WKBase.h */ - -#undef JS_EXPORT -#if defined(JS_NO_EXPORT) -#define JS_EXPORT -#elif defined(__GNUC__) && !defined(__CC_ARM) && !defined(__ARMCC__) -#define JS_EXPORT __attribute__((visibility("default"))) -#elif defined(WIN32) || defined(_WIN32) || defined(_WIN32_WCE) || defined(__CC_ARM) || defined(__ARMCC__) -#if defined(BUILDING_JavaScriptCore) || defined(STATICALLY_LINKED_WITH_JavaScriptCore) -#define JS_EXPORT __declspec(dllexport) -#else -#define JS_EXPORT __declspec(dllimport) -#endif -#else /* !defined(JS_NO_EXPORT) */ -#define JS_EXPORT -#endif /* defined(JS_NO_EXPORT) */ - -/* JS tests uses WTF but has no config.h, so we need to set the export defines here. */ -#ifndef WTF_EXPORT_PRIVATE -#define WTF_EXPORT_PRIVATE JS_EXPORT -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* Script Evaluation */ - -/*! -@function JSEvaluateScript -@abstract Evaluates a string of JavaScript. -@param ctx The execution context to use. -@param script A JSString containing the script to evaluate. -@param thisObject The object to use as "this," or NULL to use the global object as "this." -@param sourceURL A JSString containing a URL for the script's source file. This is used by debuggers and when reporting exceptions. Pass NULL if you do not care to include source file information. -@param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions. The value is one-based, so the first line is line 1 and invalid values are clamped to 1. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result The JSValue that results from evaluating script, or NULL if an exception is thrown. -*/ -JS_EXPORT JSValueRef JSEvaluateScript(JSContextRef ctx, JSStringRef script, JSObjectRef thisObject, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception); - -/*! -@function JSCheckScriptSyntax -@abstract Checks for syntax errors in a string of JavaScript. -@param ctx The execution context to use. -@param script A JSString containing the script to check for syntax errors. -@param sourceURL A JSString containing a URL for the script's source file. This is only used when reporting exceptions. Pass NULL if you do not care to include source file information in exceptions. -@param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions. The value is one-based, so the first line is line 1 and invalid values are clamped to 1. -@param exception A pointer to a JSValueRef in which to store a syntax error exception, if any. Pass NULL if you do not care to store a syntax error exception. -@result true if the script is syntactically correct, otherwise false. -*/ -JS_EXPORT bool JSCheckScriptSyntax(JSContextRef ctx, JSStringRef script, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception); - -/*! -@function JSGarbageCollect -@abstract Performs a JavaScript garbage collection. -@param ctx The execution context to use. -@discussion JavaScript values that are on the machine stack, in a register, - protected by JSValueProtect, set as the global object of an execution context, - or reachable from any such value will not be collected. - - During JavaScript execution, you are not required to call this function; the - JavaScript engine will garbage collect as needed. JavaScript values created - within a context group are automatically destroyed when the last reference - to the context group is released. -*/ -JS_EXPORT void JSGarbageCollect(JSContextRef ctx); - -#ifdef __cplusplus -} -#endif - -/* Enable the Objective-C API for platforms with a modern runtime. */ -#if !defined(JSC_OBJC_API_ENABLED) -#ifndef JSC_OBJC_API_AVAILABLE_MAC_OS_X_1080 -#define JSC_OBJC_API_ENABLED (defined(__clang__) && defined(__APPLE__) && ((defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1090 && !defined(__i386__)) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE))) -#else -#define JSC_OBJC_API_ENABLED (defined(__clang__) && defined(__APPLE__) && ((defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 && !defined(__i386__)) || (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE))) -#endif -#endif - -#endif /* JSBase_h */ diff --git a/src/jsc/include/JavaScriptCore/JSContextRef.h b/src/jsc/include/JavaScriptCore/JSContextRef.h deleted file mode 100644 index cb25c0007d..0000000000 --- a/src/jsc/include/JavaScriptCore/JSContextRef.h +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2006 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef JSContextRef_h -#define JSContextRef_h - -#include -#include -#include - -#ifndef __cplusplus -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/*! -@function -@abstract Creates a JavaScript context group. -@discussion A JSContextGroup associates JavaScript contexts with one another. - Contexts in the same group may share and exchange JavaScript objects. Sharing and/or exchanging - JavaScript objects between contexts in different groups will produce undefined behavior. - When objects from the same context group are used in multiple threads, explicit - synchronization is required. -@result The created JSContextGroup. -*/ -JS_EXPORT JSContextGroupRef JSContextGroupCreate() CF_AVAILABLE(10_6, 7_0); - -/*! -@function -@abstract Retains a JavaScript context group. -@param group The JSContextGroup to retain. -@result A JSContextGroup that is the same as group. -*/ -JS_EXPORT JSContextGroupRef JSContextGroupRetain(JSContextGroupRef group) CF_AVAILABLE(10_6, 7_0); - -/*! -@function -@abstract Releases a JavaScript context group. -@param group The JSContextGroup to release. -*/ -JS_EXPORT void JSContextGroupRelease(JSContextGroupRef group) CF_AVAILABLE(10_6, 7_0); - -/*! -@function -@abstract Creates a global JavaScript execution context. -@discussion JSGlobalContextCreate allocates a global object and populates it with all the - built-in JavaScript objects, such as Object, Function, String, and Array. - - In WebKit version 4.0 and later, the context is created in a unique context group. - Therefore, scripts may execute in it concurrently with scripts executing in other contexts. - However, you may not use values created in the context in other contexts. -@param globalObjectClass The class to use when creating the global object. Pass - NULL to use the default object class. -@result A JSGlobalContext with a global object of class globalObjectClass. -*/ -JS_EXPORT JSGlobalContextRef JSGlobalContextCreate(JSClassRef globalObjectClass) CF_AVAILABLE(10_5, 7_0); - -/*! -@function -@abstract Creates a global JavaScript execution context in the context group provided. -@discussion JSGlobalContextCreateInGroup allocates a global object and populates it with - all the built-in JavaScript objects, such as Object, Function, String, and Array. -@param globalObjectClass The class to use when creating the global object. Pass - NULL to use the default object class. -@param group The context group to use. The created global context retains the group. - Pass NULL to create a unique group for the context. -@result A JSGlobalContext with a global object of class globalObjectClass and a context - group equal to group. -*/ -JS_EXPORT JSGlobalContextRef JSGlobalContextCreateInGroup(JSContextGroupRef group, JSClassRef globalObjectClass) CF_AVAILABLE(10_6, 7_0); - -/*! -@function -@abstract Retains a global JavaScript execution context. -@param ctx The JSGlobalContext to retain. -@result A JSGlobalContext that is the same as ctx. -*/ -JS_EXPORT JSGlobalContextRef JSGlobalContextRetain(JSGlobalContextRef ctx); - -/*! -@function -@abstract Releases a global JavaScript execution context. -@param ctx The JSGlobalContext to release. -*/ -JS_EXPORT void JSGlobalContextRelease(JSGlobalContextRef ctx); - -/*! -@function -@abstract Gets the global object of a JavaScript execution context. -@param ctx The JSContext whose global object you want to get. -@result ctx's global object. -*/ -JS_EXPORT JSObjectRef JSContextGetGlobalObject(JSContextRef ctx); - -/*! -@function -@abstract Gets the context group to which a JavaScript execution context belongs. -@param ctx The JSContext whose group you want to get. -@result ctx's group. -*/ -JS_EXPORT JSContextGroupRef JSContextGetGroup(JSContextRef ctx) CF_AVAILABLE(10_6, 7_0); - -/*! -@function -@abstract Gets the global context of a JavaScript execution context. -@param ctx The JSContext whose global context you want to get. -@result ctx's global context. -*/ -JS_EXPORT JSGlobalContextRef JSContextGetGlobalContext(JSContextRef ctx) CF_AVAILABLE(10_7, 7_0); - -/*! -@function -@abstract Gets a copy of the name of a context. -@param ctx The JSGlobalContext whose name you want to get. -@result The name for ctx. -@discussion A JSGlobalContext's name is exposed for remote debugging to make it -easier to identify the context you would like to attach to. -*/ -JS_EXPORT JSStringRef JSGlobalContextCopyName(JSGlobalContextRef ctx) CF_AVAILABLE(10_10, 8_0); - -/*! -@function -@abstract Sets the remote debugging name for a context. -@param ctx The JSGlobalContext that you want to name. -@param name The remote debugging name to set on ctx. -*/ -JS_EXPORT void JSGlobalContextSetName(JSGlobalContextRef ctx, JSStringRef name) CF_AVAILABLE(10_10, 8_0); - -#ifdef __cplusplus -} -#endif - -#endif /* JSContextRef_h */ diff --git a/src/jsc/include/JavaScriptCore/JSObjectRef.h b/src/jsc/include/JavaScriptCore/JSObjectRef.h deleted file mode 100644 index 754dff3630..0000000000 --- a/src/jsc/include/JavaScriptCore/JSObjectRef.h +++ /dev/null @@ -1,694 +0,0 @@ -/* - * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. - * Copyright (C) 2008 Kelvin W Sherlock (ksherlock@gmail.com) - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef JSObjectRef_h -#define JSObjectRef_h - -#include -#include -#include - -#ifndef __cplusplus -#include -#endif -#include /* for size_t */ - -#ifdef __cplusplus -extern "C" { -#endif - -/*! -@enum JSPropertyAttribute -@constant kJSPropertyAttributeNone Specifies that a property has no special attributes. -@constant kJSPropertyAttributeReadOnly Specifies that a property is read-only. -@constant kJSPropertyAttributeDontEnum Specifies that a property should not be enumerated by JSPropertyEnumerators and JavaScript for...in loops. -@constant kJSPropertyAttributeDontDelete Specifies that the delete operation should fail on a property. -*/ -enum { - kJSPropertyAttributeNone = 0, - kJSPropertyAttributeReadOnly = 1 << 1, - kJSPropertyAttributeDontEnum = 1 << 2, - kJSPropertyAttributeDontDelete = 1 << 3 -}; - -/*! -@typedef JSPropertyAttributes -@abstract A set of JSPropertyAttributes. Combine multiple attributes by logically ORing them together. -*/ -typedef unsigned JSPropertyAttributes; - -/*! -@enum JSClassAttribute -@constant kJSClassAttributeNone Specifies that a class has no special attributes. -@constant kJSClassAttributeNoAutomaticPrototype Specifies that a class should not automatically generate a shared prototype for its instance objects. Use kJSClassAttributeNoAutomaticPrototype in combination with JSObjectSetPrototype to manage prototypes manually. -*/ -enum { - kJSClassAttributeNone = 0, - kJSClassAttributeNoAutomaticPrototype = 1 << 1 -}; - -/*! -@typedef JSClassAttributes -@abstract A set of JSClassAttributes. Combine multiple attributes by logically ORing them together. -*/ -typedef unsigned JSClassAttributes; - -/*! -@typedef JSObjectInitializeCallback -@abstract The callback invoked when an object is first created. -@param ctx The execution context to use. -@param object The JSObject being created. -@discussion If you named your function Initialize, you would declare it like this: - -void Initialize(JSContextRef ctx, JSObjectRef object); - -Unlike the other object callbacks, the initialize callback is called on the least -derived class (the parent class) first, and the most derived class last. -*/ -typedef void -(*JSObjectInitializeCallback) (JSContextRef ctx, JSObjectRef object); - -/*! -@typedef JSObjectFinalizeCallback -@abstract The callback invoked when an object is finalized (prepared for garbage collection). An object may be finalized on any thread. -@param object The JSObject being finalized. -@discussion If you named your function Finalize, you would declare it like this: - -void Finalize(JSObjectRef object); - -The finalize callback is called on the most derived class first, and the least -derived class (the parent class) last. - -You must not call any function that may cause a garbage collection or an allocation -of a garbage collected object from within a JSObjectFinalizeCallback. This includes -all functions that have a JSContextRef parameter. -*/ -typedef void -(*JSObjectFinalizeCallback) (JSObjectRef object); - -/*! -@typedef JSObjectHasPropertyCallback -@abstract The callback invoked when determining whether an object has a property. -@param ctx The execution context to use. -@param object The JSObject to search for the property. -@param propertyName A JSString containing the name of the property look up. -@result true if object has the property, otherwise false. -@discussion If you named your function HasProperty, you would declare it like this: - -bool HasProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName); - -If this function returns false, the hasProperty request forwards to object's statically declared properties, then its parent class chain (which includes the default object class), then its prototype chain. - -This callback enables optimization in cases where only a property's existence needs to be known, not its value, and computing its value would be expensive. - -If this callback is NULL, the getProperty callback will be used to service hasProperty requests. -*/ -typedef bool -(*JSObjectHasPropertyCallback) (JSContextRef ctx, JSObjectRef object, JSStringRef propertyName); - -/*! -@typedef JSObjectGetPropertyCallback -@abstract The callback invoked when getting a property's value. -@param ctx The execution context to use. -@param object The JSObject to search for the property. -@param propertyName A JSString containing the name of the property to get. -@param exception A pointer to a JSValueRef in which to return an exception, if any. -@result The property's value if object has the property, otherwise NULL. -@discussion If you named your function GetProperty, you would declare it like this: - -JSValueRef GetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); - -If this function returns NULL, the get request forwards to object's statically declared properties, then its parent class chain (which includes the default object class), then its prototype chain. -*/ -typedef JSValueRef -(*JSObjectGetPropertyCallback) (JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); - -/*! -@typedef JSObjectSetPropertyCallback -@abstract The callback invoked when setting a property's value. -@param ctx The execution context to use. -@param object The JSObject on which to set the property's value. -@param propertyName A JSString containing the name of the property to set. -@param value A JSValue to use as the property's value. -@param exception A pointer to a JSValueRef in which to return an exception, if any. -@result true if the property was set, otherwise false. -@discussion If you named your function SetProperty, you would declare it like this: - -bool SetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception); - -If this function returns false, the set request forwards to object's statically declared properties, then its parent class chain (which includes the default object class). -*/ -typedef bool -(*JSObjectSetPropertyCallback) (JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception); - -/*! -@typedef JSObjectDeletePropertyCallback -@abstract The callback invoked when deleting a property. -@param ctx The execution context to use. -@param object The JSObject in which to delete the property. -@param propertyName A JSString containing the name of the property to delete. -@param exception A pointer to a JSValueRef in which to return an exception, if any. -@result true if propertyName was successfully deleted, otherwise false. -@discussion If you named your function DeleteProperty, you would declare it like this: - -bool DeleteProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); - -If this function returns false, the delete request forwards to object's statically declared properties, then its parent class chain (which includes the default object class). -*/ -typedef bool -(*JSObjectDeletePropertyCallback) (JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); - -/*! -@typedef JSObjectGetPropertyNamesCallback -@abstract The callback invoked when collecting the names of an object's properties. -@param ctx The execution context to use. -@param object The JSObject whose property names are being collected. -@param accumulator A JavaScript property name accumulator in which to accumulate the names of object's properties. -@discussion If you named your function GetPropertyNames, you would declare it like this: - -void GetPropertyNames(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames); - -Property name accumulators are used by JSObjectCopyPropertyNames and JavaScript for...in loops. - -Use JSPropertyNameAccumulatorAddName to add property names to accumulator. A class's getPropertyNames callback only needs to provide the names of properties that the class vends through a custom getProperty or setProperty callback. Other properties, including statically declared properties, properties vended by other classes, and properties belonging to object's prototype, are added independently. -*/ -typedef void -(*JSObjectGetPropertyNamesCallback) (JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef propertyNames); - -/*! -@typedef JSObjectCallAsFunctionCallback -@abstract The callback invoked when an object is called as a function. -@param ctx The execution context to use. -@param function A JSObject that is the function being called. -@param thisObject A JSObject that is the 'this' variable in the function's scope. -@param argumentCount An integer count of the number of arguments in arguments. -@param arguments A JSValue array of the arguments passed to the function. -@param exception A pointer to a JSValueRef in which to return an exception, if any. -@result A JSValue that is the function's return value. -@discussion If you named your function CallAsFunction, you would declare it like this: - -JSValueRef CallAsFunction(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - -If your callback were invoked by the JavaScript expression 'myObject.myFunction()', function would be set to myFunction, and thisObject would be set to myObject. - -If this callback is NULL, calling your object as a function will throw an exception. -*/ -typedef JSValueRef -(*JSObjectCallAsFunctionCallback) (JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - -/*! -@typedef JSObjectCallAsConstructorCallback -@abstract The callback invoked when an object is used as a constructor in a 'new' expression. -@param ctx The execution context to use. -@param constructor A JSObject that is the constructor being called. -@param argumentCount An integer count of the number of arguments in arguments. -@param arguments A JSValue array of the arguments passed to the function. -@param exception A pointer to a JSValueRef in which to return an exception, if any. -@result A JSObject that is the constructor's return value. -@discussion If you named your function CallAsConstructor, you would declare it like this: - -JSObjectRef CallAsConstructor(JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - -If your callback were invoked by the JavaScript expression 'new myConstructor()', constructor would be set to myConstructor. - -If this callback is NULL, using your object as a constructor in a 'new' expression will throw an exception. -*/ -typedef JSObjectRef -(*JSObjectCallAsConstructorCallback) (JSContextRef ctx, JSObjectRef constructor, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - -/*! -@typedef JSObjectHasInstanceCallback -@abstract hasInstance The callback invoked when an object is used as the target of an 'instanceof' expression. -@param ctx The execution context to use. -@param constructor The JSObject that is the target of the 'instanceof' expression. -@param possibleInstance The JSValue being tested to determine if it is an instance of constructor. -@param exception A pointer to a JSValueRef in which to return an exception, if any. -@result true if possibleInstance is an instance of constructor, otherwise false. -@discussion If you named your function HasInstance, you would declare it like this: - -bool HasInstance(JSContextRef ctx, JSObjectRef constructor, JSValueRef possibleInstance, JSValueRef* exception); - -If your callback were invoked by the JavaScript expression 'someValue instanceof myObject', constructor would be set to myObject and possibleInstance would be set to someValue. - -If this callback is NULL, 'instanceof' expressions that target your object will return false. - -Standard JavaScript practice calls for objects that implement the callAsConstructor callback to implement the hasInstance callback as well. -*/ -typedef bool -(*JSObjectHasInstanceCallback) (JSContextRef ctx, JSObjectRef constructor, JSValueRef possibleInstance, JSValueRef* exception); - -/*! -@typedef JSObjectConvertToTypeCallback -@abstract The callback invoked when converting an object to a particular JavaScript type. -@param ctx The execution context to use. -@param object The JSObject to convert. -@param type A JSType specifying the JavaScript type to convert to. -@param exception A pointer to a JSValueRef in which to return an exception, if any. -@result The objects's converted value, or NULL if the object was not converted. -@discussion If you named your function ConvertToType, you would declare it like this: - -JSValueRef ConvertToType(JSContextRef ctx, JSObjectRef object, JSType type, JSValueRef* exception); - -If this function returns false, the conversion request forwards to object's parent class chain (which includes the default object class). - -This function is only invoked when converting an object to number or string. An object converted to boolean is 'true.' An object converted to object is itself. -*/ -typedef JSValueRef -(*JSObjectConvertToTypeCallback) (JSContextRef ctx, JSObjectRef object, JSType type, JSValueRef* exception); - -/*! -@struct JSStaticValue -@abstract This structure describes a statically declared value property. -@field name A null-terminated UTF8 string containing the property's name. -@field getProperty A JSObjectGetPropertyCallback to invoke when getting the property's value. -@field setProperty A JSObjectSetPropertyCallback to invoke when setting the property's value. May be NULL if the ReadOnly attribute is set. -@field attributes A logically ORed set of JSPropertyAttributes to give to the property. -*/ -typedef struct { - const char* name; - JSObjectGetPropertyCallback getProperty; - JSObjectSetPropertyCallback setProperty; - JSPropertyAttributes attributes; -} JSStaticValue; - -/*! -@struct JSStaticFunction -@abstract This structure describes a statically declared function property. -@field name A null-terminated UTF8 string containing the property's name. -@field callAsFunction A JSObjectCallAsFunctionCallback to invoke when the property is called as a function. -@field attributes A logically ORed set of JSPropertyAttributes to give to the property. -*/ -typedef struct { - const char* name; - JSObjectCallAsFunctionCallback callAsFunction; - JSPropertyAttributes attributes; -} JSStaticFunction; - -/*! -@struct JSClassDefinition -@abstract This structure contains properties and callbacks that define a type of object. All fields other than the version field are optional. Any pointer may be NULL. -@field version The version number of this structure. The current version is 0. -@field attributes A logically ORed set of JSClassAttributes to give to the class. -@field className A null-terminated UTF8 string containing the class's name. -@field parentClass A JSClass to set as the class's parent class. Pass NULL use the default object class. -@field staticValues A JSStaticValue array containing the class's statically declared value properties. Pass NULL to specify no statically declared value properties. The array must be terminated by a JSStaticValue whose name field is NULL. -@field staticFunctions A JSStaticFunction array containing the class's statically declared function properties. Pass NULL to specify no statically declared function properties. The array must be terminated by a JSStaticFunction whose name field is NULL. -@field initialize The callback invoked when an object is first created. Use this callback to initialize the object. -@field finalize The callback invoked when an object is finalized (prepared for garbage collection). Use this callback to release resources allocated for the object, and perform other cleanup. -@field hasProperty The callback invoked when determining whether an object has a property. If this field is NULL, getProperty is called instead. The hasProperty callback enables optimization in cases where only a property's existence needs to be known, not its value, and computing its value is expensive. -@field getProperty The callback invoked when getting a property's value. -@field setProperty The callback invoked when setting a property's value. -@field deleteProperty The callback invoked when deleting a property. -@field getPropertyNames The callback invoked when collecting the names of an object's properties. -@field callAsFunction The callback invoked when an object is called as a function. -@field hasInstance The callback invoked when an object is used as the target of an 'instanceof' expression. -@field callAsConstructor The callback invoked when an object is used as a constructor in a 'new' expression. -@field convertToType The callback invoked when converting an object to a particular JavaScript type. -@discussion The staticValues and staticFunctions arrays are the simplest and most efficient means for vending custom properties. Statically declared properties autmatically service requests like getProperty, setProperty, and getPropertyNames. Property access callbacks are required only to implement unusual properties, like array indexes, whose names are not known at compile-time. - -If you named your getter function "GetX" and your setter function "SetX", you would declare a JSStaticValue array containing "X" like this: - -JSStaticValue StaticValueArray[] = { - { "X", GetX, SetX, kJSPropertyAttributeNone }, - { 0, 0, 0, 0 } -}; - -Standard JavaScript practice calls for storing function objects in prototypes, so they can be shared. The default JSClass created by JSClassCreate follows this idiom, instantiating objects with a shared, automatically generating prototype containing the class's function objects. The kJSClassAttributeNoAutomaticPrototype attribute specifies that a JSClass should not automatically generate such a prototype. The resulting JSClass instantiates objects with the default object prototype, and gives each instance object its own copy of the class's function objects. - -A NULL callback specifies that the default object callback should substitute, except in the case of hasProperty, where it specifies that getProperty should substitute. -*/ -typedef struct { - int version; /* current (and only) version is 0 */ - JSClassAttributes attributes; - - const char* className; - JSClassRef parentClass; - - const JSStaticValue* staticValues; - const JSStaticFunction* staticFunctions; - - JSObjectInitializeCallback initialize; - JSObjectFinalizeCallback finalize; - JSObjectHasPropertyCallback hasProperty; - JSObjectGetPropertyCallback getProperty; - JSObjectSetPropertyCallback setProperty; - JSObjectDeletePropertyCallback deleteProperty; - JSObjectGetPropertyNamesCallback getPropertyNames; - JSObjectCallAsFunctionCallback callAsFunction; - JSObjectCallAsConstructorCallback callAsConstructor; - JSObjectHasInstanceCallback hasInstance; - JSObjectConvertToTypeCallback convertToType; -} JSClassDefinition; - -/*! -@const kJSClassDefinitionEmpty -@abstract A JSClassDefinition structure of the current version, filled with NULL pointers and having no attributes. -@discussion Use this constant as a convenience when creating class definitions. For example, to create a class definition with only a finalize method: - -JSClassDefinition definition = kJSClassDefinitionEmpty; -definition.finalize = Finalize; -*/ -JS_EXPORT extern const JSClassDefinition kJSClassDefinitionEmpty; - -/*! -@function -@abstract Creates a JavaScript class suitable for use with JSObjectMake. -@param definition A JSClassDefinition that defines the class. -@result A JSClass with the given definition. Ownership follows the Create Rule. -*/ -JS_EXPORT JSClassRef JSClassCreate(const JSClassDefinition* definition); - -/*! -@function -@abstract Retains a JavaScript class. -@param jsClass The JSClass to retain. -@result A JSClass that is the same as jsClass. -*/ -JS_EXPORT JSClassRef JSClassRetain(JSClassRef jsClass); - -/*! -@function -@abstract Releases a JavaScript class. -@param jsClass The JSClass to release. -*/ -JS_EXPORT void JSClassRelease(JSClassRef jsClass); - -/*! -@function -@abstract Creates a JavaScript object. -@param ctx The execution context to use. -@param jsClass The JSClass to assign to the object. Pass NULL to use the default object class. -@param data A void* to set as the object's private data. Pass NULL to specify no private data. -@result A JSObject with the given class and private data. -@discussion The default object class does not allocate storage for private data, so you must provide a non-NULL jsClass to JSObjectMake if you want your object to be able to store private data. - -data is set on the created object before the intialize methods in its class chain are called. This enables the initialize methods to retrieve and manipulate data through JSObjectGetPrivate. -*/ -JS_EXPORT JSObjectRef JSObjectMake(JSContextRef ctx, JSClassRef jsClass, void* data); - -/*! -@function -@abstract Convenience method for creating a JavaScript function with a given callback as its implementation. -@param ctx The execution context to use. -@param name A JSString containing the function's name. This will be used when converting the function to string. Pass NULL to create an anonymous function. -@param callAsFunction The JSObjectCallAsFunctionCallback to invoke when the function is called. -@result A JSObject that is a function. The object's prototype will be the default function prototype. -*/ -JS_EXPORT JSObjectRef JSObjectMakeFunctionWithCallback(JSContextRef ctx, JSStringRef name, JSObjectCallAsFunctionCallback callAsFunction); - -/*! -@function -@abstract Convenience method for creating a JavaScript constructor. -@param ctx The execution context to use. -@param jsClass A JSClass that is the class your constructor will assign to the objects its constructs. jsClass will be used to set the constructor's .prototype property, and to evaluate 'instanceof' expressions. Pass NULL to use the default object class. -@param callAsConstructor A JSObjectCallAsConstructorCallback to invoke when your constructor is used in a 'new' expression. Pass NULL to use the default object constructor. -@result A JSObject that is a constructor. The object's prototype will be the default object prototype. -@discussion The default object constructor takes no arguments and constructs an object of class jsClass with no private data. -*/ -JS_EXPORT JSObjectRef JSObjectMakeConstructor(JSContextRef ctx, JSClassRef jsClass, JSObjectCallAsConstructorCallback callAsConstructor); - -/*! - @function - @abstract Creates a JavaScript Array object. - @param ctx The execution context to use. - @param argumentCount An integer count of the number of arguments in arguments. - @param arguments A JSValue array of data to populate the Array with. Pass NULL if argumentCount is 0. - @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. - @result A JSObject that is an Array. - @discussion The behavior of this function does not exactly match the behavior of the built-in Array constructor. Specifically, if one argument - is supplied, this function returns an array with one element. - */ -JS_EXPORT JSObjectRef JSObjectMakeArray(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) CF_AVAILABLE(10_6, 7_0); - -/*! - @function - @abstract Creates a JavaScript Date object, as if by invoking the built-in Date constructor. - @param ctx The execution context to use. - @param argumentCount An integer count of the number of arguments in arguments. - @param arguments A JSValue array of arguments to pass to the Date Constructor. Pass NULL if argumentCount is 0. - @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. - @result A JSObject that is a Date. - */ -JS_EXPORT JSObjectRef JSObjectMakeDate(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) CF_AVAILABLE(10_6, 7_0); - -/*! - @function - @abstract Creates a JavaScript Error object, as if by invoking the built-in Error constructor. - @param ctx The execution context to use. - @param argumentCount An integer count of the number of arguments in arguments. - @param arguments A JSValue array of arguments to pass to the Error Constructor. Pass NULL if argumentCount is 0. - @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. - @result A JSObject that is a Error. - */ -JS_EXPORT JSObjectRef JSObjectMakeError(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) CF_AVAILABLE(10_6, 7_0); - -/*! - @function - @abstract Creates a JavaScript RegExp object, as if by invoking the built-in RegExp constructor. - @param ctx The execution context to use. - @param argumentCount An integer count of the number of arguments in arguments. - @param arguments A JSValue array of arguments to pass to the RegExp Constructor. Pass NULL if argumentCount is 0. - @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. - @result A JSObject that is a RegExp. - */ -JS_EXPORT JSObjectRef JSObjectMakeRegExp(JSContextRef ctx, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) CF_AVAILABLE(10_6, 7_0); - -/*! -@function -@abstract Creates a function with a given script as its body. -@param ctx The execution context to use. -@param name A JSString containing the function's name. This will be used when converting the function to string. Pass NULL to create an anonymous function. -@param parameterCount An integer count of the number of parameter names in parameterNames. -@param parameterNames A JSString array containing the names of the function's parameters. Pass NULL if parameterCount is 0. -@param body A JSString containing the script to use as the function's body. -@param sourceURL A JSString containing a URL for the script's source file. This is only used when reporting exceptions. Pass NULL if you do not care to include source file information in exceptions. -@param startingLineNumber An integer value specifying the script's starting line number in the file located at sourceURL. This is only used when reporting exceptions. The value is one-based, so the first line is line 1 and invalid values are clamped to 1. -@param exception A pointer to a JSValueRef in which to store a syntax error exception, if any. Pass NULL if you do not care to store a syntax error exception. -@result A JSObject that is a function, or NULL if either body or parameterNames contains a syntax error. The object's prototype will be the default function prototype. -@discussion Use this method when you want to execute a script repeatedly, to avoid the cost of re-parsing the script before each execution. -*/ -JS_EXPORT JSObjectRef JSObjectMakeFunction(JSContextRef ctx, JSStringRef name, unsigned parameterCount, const JSStringRef parameterNames[], JSStringRef body, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception); - -/*! -@function -@abstract Gets an object's prototype. -@param ctx The execution context to use. -@param object A JSObject whose prototype you want to get. -@result A JSValue that is the object's prototype. -*/ -JS_EXPORT JSValueRef JSObjectGetPrototype(JSContextRef ctx, JSObjectRef object); - -/*! -@function -@abstract Sets an object's prototype. -@param ctx The execution context to use. -@param object The JSObject whose prototype you want to set. -@param value A JSValue to set as the object's prototype. -*/ -JS_EXPORT void JSObjectSetPrototype(JSContextRef ctx, JSObjectRef object, JSValueRef value); - -/*! -@function -@abstract Tests whether an object has a given property. -@param object The JSObject to test. -@param propertyName A JSString containing the property's name. -@result true if the object has a property whose name matches propertyName, otherwise false. -*/ -JS_EXPORT bool JSObjectHasProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName); - -/*! -@function -@abstract Gets a property from an object. -@param ctx The execution context to use. -@param object The JSObject whose property you want to get. -@param propertyName A JSString containing the property's name. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result The property's value if object has the property, otherwise the undefined value. -*/ -JS_EXPORT JSValueRef JSObjectGetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); - -/*! -@function -@abstract Sets a property on an object. -@param ctx The execution context to use. -@param object The JSObject whose property you want to set. -@param propertyName A JSString containing the property's name. -@param value A JSValue to use as the property's value. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@param attributes A logically ORed set of JSPropertyAttributes to give to the property. -*/ -JS_EXPORT void JSObjectSetProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSPropertyAttributes attributes, JSValueRef* exception); - -/*! -@function -@abstract Deletes a property from an object. -@param ctx The execution context to use. -@param object The JSObject whose property you want to delete. -@param propertyName A JSString containing the property's name. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result true if the delete operation succeeds, otherwise false (for example, if the property has the kJSPropertyAttributeDontDelete attribute set). -*/ -JS_EXPORT bool JSObjectDeleteProperty(JSContextRef ctx, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception); - -/*! -@function -@abstract Gets a property from an object by numeric index. -@param ctx The execution context to use. -@param object The JSObject whose property you want to get. -@param propertyIndex An integer value that is the property's name. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result The property's value if object has the property, otherwise the undefined value. -@discussion Calling JSObjectGetPropertyAtIndex is equivalent to calling JSObjectGetProperty with a string containing propertyIndex, but JSObjectGetPropertyAtIndex provides optimized access to numeric properties. -*/ -JS_EXPORT JSValueRef JSObjectGetPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned propertyIndex, JSValueRef* exception); - -/*! -@function -@abstract Sets a property on an object by numeric index. -@param ctx The execution context to use. -@param object The JSObject whose property you want to set. -@param propertyIndex The property's name as a number. -@param value A JSValue to use as the property's value. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@discussion Calling JSObjectSetPropertyAtIndex is equivalent to calling JSObjectSetProperty with a string containing propertyIndex, but JSObjectSetPropertyAtIndex provides optimized access to numeric properties. -*/ -JS_EXPORT void JSObjectSetPropertyAtIndex(JSContextRef ctx, JSObjectRef object, unsigned propertyIndex, JSValueRef value, JSValueRef* exception); - -/*! -@function -@abstract Gets an object's private data. -@param object A JSObject whose private data you want to get. -@result A void* that is the object's private data, if the object has private data, otherwise NULL. -*/ -JS_EXPORT void* JSObjectGetPrivate(JSObjectRef object); - -/*! -@function -@abstract Sets a pointer to private data on an object. -@param object The JSObject whose private data you want to set. -@param data A void* to set as the object's private data. -@result true if object can store private data, otherwise false. -@discussion The default object class does not allocate storage for private data. Only objects created with a non-NULL JSClass can store private data. -*/ -JS_EXPORT bool JSObjectSetPrivate(JSObjectRef object, void* data); - -/*! -@function -@abstract Tests whether an object can be called as a function. -@param ctx The execution context to use. -@param object The JSObject to test. -@result true if the object can be called as a function, otherwise false. -*/ -JS_EXPORT bool JSObjectIsFunction(JSContextRef ctx, JSObjectRef object); - -/*! -@function -@abstract Calls an object as a function. -@param ctx The execution context to use. -@param object The JSObject to call as a function. -@param thisObject The object to use as "this," or NULL to use the global object as "this." -@param argumentCount An integer count of the number of arguments in arguments. -@param arguments A JSValue array of arguments to pass to the function. Pass NULL if argumentCount is 0. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result The JSValue that results from calling object as a function, or NULL if an exception is thrown or object is not a function. -*/ -JS_EXPORT JSValueRef JSObjectCallAsFunction(JSContextRef ctx, JSObjectRef object, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - -/*! -@function -@abstract Tests whether an object can be called as a constructor. -@param ctx The execution context to use. -@param object The JSObject to test. -@result true if the object can be called as a constructor, otherwise false. -*/ -JS_EXPORT bool JSObjectIsConstructor(JSContextRef ctx, JSObjectRef object); - -/*! -@function -@abstract Calls an object as a constructor. -@param ctx The execution context to use. -@param object The JSObject to call as a constructor. -@param argumentCount An integer count of the number of arguments in arguments. -@param arguments A JSValue array of arguments to pass to the constructor. Pass NULL if argumentCount is 0. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result The JSObject that results from calling object as a constructor, or NULL if an exception is thrown or object is not a constructor. -*/ -JS_EXPORT JSObjectRef JSObjectCallAsConstructor(JSContextRef ctx, JSObjectRef object, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception); - -/*! -@function -@abstract Gets the names of an object's enumerable properties. -@param ctx The execution context to use. -@param object The object whose property names you want to get. -@result A JSPropertyNameArray containing the names object's enumerable properties. Ownership follows the Create Rule. -*/ -JS_EXPORT JSPropertyNameArrayRef JSObjectCopyPropertyNames(JSContextRef ctx, JSObjectRef object); - -/*! -@function -@abstract Retains a JavaScript property name array. -@param array The JSPropertyNameArray to retain. -@result A JSPropertyNameArray that is the same as array. -*/ -JS_EXPORT JSPropertyNameArrayRef JSPropertyNameArrayRetain(JSPropertyNameArrayRef array); - -/*! -@function -@abstract Releases a JavaScript property name array. -@param array The JSPropetyNameArray to release. -*/ -JS_EXPORT void JSPropertyNameArrayRelease(JSPropertyNameArrayRef array); - -/*! -@function -@abstract Gets a count of the number of items in a JavaScript property name array. -@param array The array from which to retrieve the count. -@result An integer count of the number of names in array. -*/ -JS_EXPORT size_t JSPropertyNameArrayGetCount(JSPropertyNameArrayRef array); - -/*! -@function -@abstract Gets a property name at a given index in a JavaScript property name array. -@param array The array from which to retrieve the property name. -@param index The index of the property name to retrieve. -@result A JSStringRef containing the property name. -*/ -JS_EXPORT JSStringRef JSPropertyNameArrayGetNameAtIndex(JSPropertyNameArrayRef array, size_t index); - -/*! -@function -@abstract Adds a property name to a JavaScript property name accumulator. -@param accumulator The accumulator object to which to add the property name. -@param propertyName The property name to add. -*/ -JS_EXPORT void JSPropertyNameAccumulatorAddName(JSPropertyNameAccumulatorRef accumulator, JSStringRef propertyName); - -#ifdef __cplusplus -} -#endif - -#endif /* JSObjectRef_h */ diff --git a/src/jsc/include/JavaScriptCore/JSRetainPtr.h b/src/jsc/include/JavaScriptCore/JSRetainPtr.h deleted file mode 100644 index f23e32fcb7..0000000000 --- a/src/jsc/include/JavaScriptCore/JSRetainPtr.h +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (C) 2005, 2006, 2007, 2010 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of Apple Inc. ("Apple") nor the names of - * its contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef JSRetainPtr_h -#define JSRetainPtr_h - -#include -#include -#include - -inline void JSRetain(JSStringRef string) { JSStringRetain(string); } -inline void JSRelease(JSStringRef string) { JSStringRelease(string); } -inline void JSRetain(JSGlobalContextRef context) { JSGlobalContextRetain(context); } -inline void JSRelease(JSGlobalContextRef context) { JSGlobalContextRelease(context); } - -enum AdoptTag { Adopt }; - -template class JSRetainPtr { -public: - JSRetainPtr() : m_ptr(0) { } - JSRetainPtr(T ptr) : m_ptr(ptr) { if (ptr) JSRetain(ptr); } - JSRetainPtr(AdoptTag, T ptr) : m_ptr(ptr) { } - JSRetainPtr(const JSRetainPtr&); - template JSRetainPtr(const JSRetainPtr&); - ~JSRetainPtr(); - - T get() const { return m_ptr; } - - void clear(); - T leakRef(); - - T operator->() const { return m_ptr; } - - bool operator!() const { return !m_ptr; } - - // This conversion operator allows implicit conversion to bool but not to other integer types. - typedef T JSRetainPtr::*UnspecifiedBoolType; - operator UnspecifiedBoolType() const { return m_ptr ? &JSRetainPtr::m_ptr : 0; } - - JSRetainPtr& operator=(const JSRetainPtr&); - template JSRetainPtr& operator=(const JSRetainPtr&); - JSRetainPtr& operator=(T); - template JSRetainPtr& operator=(U*); - - void adopt(T); - - void swap(JSRetainPtr&); - -private: - T m_ptr; -}; - -template inline JSRetainPtr::JSRetainPtr(const JSRetainPtr& o) - : m_ptr(o.m_ptr) -{ - if (m_ptr) - JSRetain(m_ptr); -} - -template template inline JSRetainPtr::JSRetainPtr(const JSRetainPtr& o) - : m_ptr(o.get()) -{ - if (m_ptr) - JSRetain(m_ptr); -} - -template inline JSRetainPtr::~JSRetainPtr() -{ - if (m_ptr) - JSRelease(m_ptr); -} - -template inline void JSRetainPtr::clear() -{ - if (T ptr = m_ptr) { - m_ptr = 0; - JSRelease(ptr); - } -} - -template inline T JSRetainPtr::leakRef() -{ - T ptr = m_ptr; - m_ptr = 0; - return ptr; -} - -template inline JSRetainPtr& JSRetainPtr::operator=(const JSRetainPtr& o) -{ - T optr = o.get(); - if (optr) - JSRetain(optr); - T ptr = m_ptr; - m_ptr = optr; - if (ptr) - JSRelease(ptr); - return *this; -} - -template template inline JSRetainPtr& JSRetainPtr::operator=(const JSRetainPtr& o) -{ - T optr = o.get(); - if (optr) - JSRetain(optr); - T ptr = m_ptr; - m_ptr = optr; - if (ptr) - JSRelease(ptr); - return *this; -} - -template inline JSRetainPtr& JSRetainPtr::operator=(T optr) -{ - if (optr) - JSRetain(optr); - T ptr = m_ptr; - m_ptr = optr; - if (ptr) - JSRelease(ptr); - return *this; -} - -template inline void JSRetainPtr::adopt(T optr) -{ - T ptr = m_ptr; - m_ptr = optr; - if (ptr) - JSRelease(ptr); -} - -template template inline JSRetainPtr& JSRetainPtr::operator=(U* optr) -{ - if (optr) - JSRetain(optr); - T ptr = m_ptr; - m_ptr = optr; - if (ptr) - JSRelease(ptr); - return *this; -} - -template inline void JSRetainPtr::swap(JSRetainPtr& o) -{ - std::swap(m_ptr, o.m_ptr); -} - -template inline void swap(JSRetainPtr& a, JSRetainPtr& b) -{ - a.swap(b); -} - -template inline bool operator==(const JSRetainPtr& a, const JSRetainPtr& b) -{ - return a.get() == b.get(); -} - -template inline bool operator==(const JSRetainPtr& a, U* b) -{ - return a.get() == b; -} - -template inline bool operator==(T* a, const JSRetainPtr& b) -{ - return a == b.get(); -} - -template inline bool operator!=(const JSRetainPtr& a, const JSRetainPtr& b) -{ - return a.get() != b.get(); -} - -template inline bool operator!=(const JSRetainPtr& a, U* b) -{ - return a.get() != b; -} - -template inline bool operator!=(T* a, const JSRetainPtr& b) -{ - return a != b.get(); -} - - -#endif // JSRetainPtr_h diff --git a/src/jsc/include/JavaScriptCore/JSStringRef.h b/src/jsc/include/JavaScriptCore/JSStringRef.h deleted file mode 100644 index 6f45fcbd83..0000000000 --- a/src/jsc/include/JavaScriptCore/JSStringRef.h +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (C) 2006 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef JSStringRef_h -#define JSStringRef_h - -#include - -#ifndef __cplusplus -#include -#endif -#include /* for size_t */ - -#ifdef __cplusplus -extern "C" { -#endif - -#if !defined(WIN32) && !defined(_WIN32) \ - && !((defined(__CC_ARM) || defined(__ARMCC__)) && !defined(__linux__)) /* RVCT */ -/*! -@typedef JSChar -@abstract A Unicode character. -*/ - typedef unsigned short JSChar; -#else - typedef wchar_t JSChar; -#endif - -/*! -@function -@abstract Creates a JavaScript string from a buffer of Unicode characters. -@param chars The buffer of Unicode characters to copy into the new JSString. -@param numChars The number of characters to copy from the buffer pointed to by chars. -@result A JSString containing chars. Ownership follows the Create Rule. -*/ -JS_EXPORT JSStringRef JSStringCreateWithCharacters(const JSChar* chars, size_t numChars); -/*! -@function -@abstract Creates a JavaScript string from a null-terminated UTF8 string. -@param string The null-terminated UTF8 string to copy into the new JSString. -@result A JSString containing string. Ownership follows the Create Rule. -*/ -JS_EXPORT JSStringRef JSStringCreateWithUTF8CString(const char* string); - -/*! -@function -@abstract Retains a JavaScript string. -@param string The JSString to retain. -@result A JSString that is the same as string. -*/ -JS_EXPORT JSStringRef JSStringRetain(JSStringRef string); -/*! -@function -@abstract Releases a JavaScript string. -@param string The JSString to release. -*/ -JS_EXPORT void JSStringRelease(JSStringRef string); - -/*! -@function -@abstract Returns the number of Unicode characters in a JavaScript string. -@param string The JSString whose length (in Unicode characters) you want to know. -@result The number of Unicode characters stored in string. -*/ -JS_EXPORT size_t JSStringGetLength(JSStringRef string); -/*! -@function -@abstract Returns a pointer to the Unicode character buffer that - serves as the backing store for a JavaScript string. -@param string The JSString whose backing store you want to access. -@result A pointer to the Unicode character buffer that serves as string's - backing store, which will be deallocated when string is deallocated. -*/ -JS_EXPORT const JSChar* JSStringGetCharactersPtr(JSStringRef string); - -/*! -@function -@abstract Returns the maximum number of bytes a JavaScript string will - take up if converted into a null-terminated UTF8 string. -@param string The JSString whose maximum converted size (in bytes) you - want to know. -@result The maximum number of bytes that could be required to convert string into a - null-terminated UTF8 string. The number of bytes that the conversion actually ends - up requiring could be less than this, but never more. -*/ -JS_EXPORT size_t JSStringGetMaximumUTF8CStringSize(JSStringRef string); -/*! -@function -@abstract Converts a JavaScript string into a null-terminated UTF8 string, - and copies the result into an external byte buffer. -@param string The source JSString. -@param buffer The destination byte buffer into which to copy a null-terminated - UTF8 representation of string. On return, buffer contains a UTF8 string - representation of string. If bufferSize is too small, buffer will contain only - partial results. If buffer is not at least bufferSize bytes in size, - behavior is undefined. -@param bufferSize The size of the external buffer in bytes. -@result The number of bytes written into buffer (including the null-terminator byte). -*/ -JS_EXPORT size_t JSStringGetUTF8CString(JSStringRef string, char* buffer, size_t bufferSize); - -/*! -@function -@abstract Tests whether two JavaScript strings match. -@param a The first JSString to test. -@param b The second JSString to test. -@result true if the two strings match, otherwise false. -*/ -JS_EXPORT bool JSStringIsEqual(JSStringRef a, JSStringRef b); -/*! -@function -@abstract Tests whether a JavaScript string matches a null-terminated UTF8 string. -@param a The JSString to test. -@param b The null-terminated UTF8 string to test. -@result true if the two strings match, otherwise false. -*/ -JS_EXPORT bool JSStringIsEqualToUTF8CString(JSStringRef a, const char* b); - -#ifdef __cplusplus -} -#endif - -#endif /* JSStringRef_h */ diff --git a/src/jsc/include/JavaScriptCore/JSValueRef.h b/src/jsc/include/JavaScriptCore/JSValueRef.h deleted file mode 100644 index 538e6e0deb..0000000000 --- a/src/jsc/include/JavaScriptCore/JSValueRef.h +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (C) 2006 Apple Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef JSValueRef_h -#define JSValueRef_h - -#include -#include - -#ifndef __cplusplus -#include -#endif - -/*! -@enum JSType -@abstract A constant identifying the type of a JSValue. -@constant kJSTypeUndefined The unique undefined value. -@constant kJSTypeNull The unique null value. -@constant kJSTypeBoolean A primitive boolean value, one of true or false. -@constant kJSTypeNumber A primitive number value. -@constant kJSTypeString A primitive string value. -@constant kJSTypeObject An object value (meaning that this JSValueRef is a JSObjectRef). -*/ -typedef enum { - kJSTypeUndefined, - kJSTypeNull, - kJSTypeBoolean, - kJSTypeNumber, - kJSTypeString, - kJSTypeObject -} JSType; - -#ifdef __cplusplus -extern "C" { -#endif - -/*! -@function -@abstract Returns a JavaScript value's type. -@param ctx The execution context to use. -@param value The JSValue whose type you want to obtain. -@result A value of type JSType that identifies value's type. -*/ -JS_EXPORT JSType JSValueGetType(JSContextRef ctx, JSValueRef); - -/*! -@function -@abstract Tests whether a JavaScript value's type is the undefined type. -@param ctx The execution context to use. -@param value The JSValue to test. -@result true if value's type is the undefined type, otherwise false. -*/ -JS_EXPORT bool JSValueIsUndefined(JSContextRef ctx, JSValueRef value); - -/*! -@function -@abstract Tests whether a JavaScript value's type is the null type. -@param ctx The execution context to use. -@param value The JSValue to test. -@result true if value's type is the null type, otherwise false. -*/ -JS_EXPORT bool JSValueIsNull(JSContextRef ctx, JSValueRef value); - -/*! -@function -@abstract Tests whether a JavaScript value's type is the boolean type. -@param ctx The execution context to use. -@param value The JSValue to test. -@result true if value's type is the boolean type, otherwise false. -*/ -JS_EXPORT bool JSValueIsBoolean(JSContextRef ctx, JSValueRef value); - -/*! -@function -@abstract Tests whether a JavaScript value's type is the number type. -@param ctx The execution context to use. -@param value The JSValue to test. -@result true if value's type is the number type, otherwise false. -*/ -JS_EXPORT bool JSValueIsNumber(JSContextRef ctx, JSValueRef value); - -/*! -@function -@abstract Tests whether a JavaScript value's type is the string type. -@param ctx The execution context to use. -@param value The JSValue to test. -@result true if value's type is the string type, otherwise false. -*/ -JS_EXPORT bool JSValueIsString(JSContextRef ctx, JSValueRef value); - -/*! -@function -@abstract Tests whether a JavaScript value's type is the object type. -@param ctx The execution context to use. -@param value The JSValue to test. -@result true if value's type is the object type, otherwise false. -*/ -JS_EXPORT bool JSValueIsObject(JSContextRef ctx, JSValueRef value); - -/*! -@function -@abstract Tests whether a JavaScript value is an object with a given class in its class chain. -@param ctx The execution context to use. -@param value The JSValue to test. -@param jsClass The JSClass to test against. -@result true if value is an object and has jsClass in its class chain, otherwise false. -*/ -JS_EXPORT bool JSValueIsObjectOfClass(JSContextRef ctx, JSValueRef value, JSClassRef jsClass); - -/* Comparing values */ - -/*! -@function -@abstract Tests whether two JavaScript values are equal, as compared by the JS == operator. -@param ctx The execution context to use. -@param a The first value to test. -@param b The second value to test. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result true if the two values are equal, false if they are not equal or an exception is thrown. -*/ -JS_EXPORT bool JSValueIsEqual(JSContextRef ctx, JSValueRef a, JSValueRef b, JSValueRef* exception); - -/*! -@function -@abstract Tests whether two JavaScript values are strict equal, as compared by the JS === operator. -@param ctx The execution context to use. -@param a The first value to test. -@param b The second value to test. -@result true if the two values are strict equal, otherwise false. -*/ -JS_EXPORT bool JSValueIsStrictEqual(JSContextRef ctx, JSValueRef a, JSValueRef b); - -/*! -@function -@abstract Tests whether a JavaScript value is an object constructed by a given constructor, as compared by the JS instanceof operator. -@param ctx The execution context to use. -@param value The JSValue to test. -@param constructor The constructor to test against. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result true if value is an object constructed by constructor, as compared by the JS instanceof operator, otherwise false. -*/ -JS_EXPORT bool JSValueIsInstanceOfConstructor(JSContextRef ctx, JSValueRef value, JSObjectRef constructor, JSValueRef* exception); - -/* Creating values */ - -/*! -@function -@abstract Creates a JavaScript value of the undefined type. -@param ctx The execution context to use. -@result The unique undefined value. -*/ -JS_EXPORT JSValueRef JSValueMakeUndefined(JSContextRef ctx); - -/*! -@function -@abstract Creates a JavaScript value of the null type. -@param ctx The execution context to use. -@result The unique null value. -*/ -JS_EXPORT JSValueRef JSValueMakeNull(JSContextRef ctx); - -/*! -@function -@abstract Creates a JavaScript value of the boolean type. -@param ctx The execution context to use. -@param boolean The bool to assign to the newly created JSValue. -@result A JSValue of the boolean type, representing the value of boolean. -*/ -JS_EXPORT JSValueRef JSValueMakeBoolean(JSContextRef ctx, bool boolean); - -/*! -@function -@abstract Creates a JavaScript value of the number type. -@param ctx The execution context to use. -@param number The double to assign to the newly created JSValue. -@result A JSValue of the number type, representing the value of number. -*/ -JS_EXPORT JSValueRef JSValueMakeNumber(JSContextRef ctx, double number); - -/*! -@function -@abstract Creates a JavaScript value of the string type. -@param ctx The execution context to use. -@param string The JSString to assign to the newly created JSValue. The - newly created JSValue retains string, and releases it upon garbage collection. -@result A JSValue of the string type, representing the value of string. -*/ -JS_EXPORT JSValueRef JSValueMakeString(JSContextRef ctx, JSStringRef string); - -/* Converting to and from JSON formatted strings */ - -/*! - @function - @abstract Creates a JavaScript value from a JSON formatted string. - @param ctx The execution context to use. - @param string The JSString containing the JSON string to be parsed. - @result A JSValue containing the parsed value, or NULL if the input is invalid. - */ -JS_EXPORT JSValueRef JSValueMakeFromJSONString(JSContextRef ctx, JSStringRef string) CF_AVAILABLE(10_7, 7_0); - -/*! - @function - @abstract Creates a JavaScript string containing the JSON serialized representation of a JS value. - @param ctx The execution context to use. - @param value The value to serialize. - @param indent The number of spaces to indent when nesting. If 0, the resulting JSON will not contains newlines. The size of the indent is clamped to 10 spaces. - @param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. - @result A JSString with the result of serialization, or NULL if an exception is thrown. - */ -JS_EXPORT JSStringRef JSValueCreateJSONString(JSContextRef ctx, JSValueRef value, unsigned indent, JSValueRef* exception) CF_AVAILABLE(10_7, 7_0); - -/* Converting to primitive values */ - -/*! -@function -@abstract Converts a JavaScript value to boolean and returns the resulting boolean. -@param ctx The execution context to use. -@param value The JSValue to convert. -@result The boolean result of conversion. -*/ -JS_EXPORT bool JSValueToBoolean(JSContextRef ctx, JSValueRef value); - -/*! -@function -@abstract Converts a JavaScript value to number and returns the resulting number. -@param ctx The execution context to use. -@param value The JSValue to convert. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result The numeric result of conversion, or NaN if an exception is thrown. -*/ -JS_EXPORT double JSValueToNumber(JSContextRef ctx, JSValueRef value, JSValueRef* exception); - -/*! -@function -@abstract Converts a JavaScript value to string and copies the result into a JavaScript string. -@param ctx The execution context to use. -@param value The JSValue to convert. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result A JSString with the result of conversion, or NULL if an exception is thrown. Ownership follows the Create Rule. -*/ -JS_EXPORT JSStringRef JSValueToStringCopy(JSContextRef ctx, JSValueRef value, JSValueRef* exception); - -/*! -@function -@abstract Converts a JavaScript value to object and returns the resulting object. -@param ctx The execution context to use. -@param value The JSValue to convert. -@param exception A pointer to a JSValueRef in which to store an exception, if any. Pass NULL if you do not care to store an exception. -@result The JSObject result of conversion, or NULL if an exception is thrown. -*/ -JS_EXPORT JSObjectRef JSValueToObject(JSContextRef ctx, JSValueRef value, JSValueRef* exception); - -/* Garbage collection */ -/*! -@function -@abstract Protects a JavaScript value from garbage collection. -@param ctx The execution context to use. -@param value The JSValue to protect. -@discussion Use this method when you want to store a JSValue in a global or on the heap, where the garbage collector will not be able to discover your reference to it. - -A value may be protected multiple times and must be unprotected an equal number of times before becoming eligible for garbage collection. -*/ -JS_EXPORT void JSValueProtect(JSContextRef ctx, JSValueRef value); - -/*! -@function -@abstract Unprotects a JavaScript value from garbage collection. -@param ctx The execution context to use. -@param value The JSValue to unprotect. -@discussion A value may be protected multiple times and must be unprotected an - equal number of times before becoming eligible for garbage collection. -*/ -JS_EXPORT void JSValueUnprotect(JSContextRef ctx, JSValueRef value); - -#ifdef __cplusplus -} -#endif - -#endif /* JSValueRef_h */ diff --git a/src/jsc/include/JavaScriptCore/WebKitAvailability.h b/src/jsc/include/JavaScriptCore/WebKitAvailability.h deleted file mode 100644 index 24695ed9a8..0000000000 --- a/src/jsc/include/JavaScriptCore/WebKitAvailability.h +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2008, 2009, 2010, 2014 Apple Inc. All Rights Reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY - * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef __WebKitAvailability__ -#define __WebKitAvailability__ - -#ifdef __APPLE__ - -#include -#include - -#if !TARGET_OS_IPHONE && __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 -/* To support availability macros that mention newer OS X versions when building on older OS X versions, - we provide our own definitions of the underlying macros that the availability macros expand to. We're - free to expand the macros as no-ops since frameworks built on older OS X versions only ship bundled with - an application rather than as part of the system. -*/ - -#ifndef __NSi_10_10 -#define __NSi_10_10 introduced=10.0 -#endif - -#ifndef __AVAILABILITY_INTERNAL__MAC_10_9 -#define __AVAILABILITY_INTERNAL__MAC_10_9 -#endif - -#ifndef __AVAILABILITY_INTERNAL__MAC_10_10 -#define __AVAILABILITY_INTERNAL__MAC_10_10 -#endif - -#ifndef AVAILABLE_MAC_OS_X_VERSION_10_9_AND_LATER -#define AVAILABLE_MAC_OS_X_VERSION_10_9_AND_LATER -#endif - -#ifndef AVAILABLE_MAC_OS_X_VERSION_10_10_AND_LATER -#define AVAILABLE_MAC_OS_X_VERSION_10_10_AND_LATER -#endif - -#endif /* __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090 */ - -#else -#define CF_AVAILABLE(_mac, _ios) -#endif - -#endif /* __WebKitAvailability__ */ diff --git a/src/jsc/jsc_class.hpp b/src/jsc/jsc_class.hpp deleted file mode 100644 index 7f4d5a17bc..0000000000 --- a/src/jsc/jsc_class.hpp +++ /dev/null @@ -1,1198 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "jsc_types.hpp" -#include "jsc_function.hpp" -#include "jsc_externs.hpp" - -#include "js_class.hpp" -#include "js_util.hpp" - -namespace realm { -namespace js { -template -struct RealmObjectClass; -} -namespace node { -struct Types; -} -} // namespace realm - -namespace realm { -namespace jsc { - -extern js::Protected ObjectDefineProperty; -extern js::Protected FunctionPrototype; -extern js::Protected RealmObjectClassConstructor; -extern js::Protected RealmObjectClassConstructorPrototype; - -static inline void jsc_class_init(JSContextRef ctx, JSObjectRef globalObject, std::function flushUiQueue) -{ - // handle ReactNative app refresh by reseting the cached constructor values - if (RealmObjectClassConstructor) { - RealmObjectClassConstructor = js::Protected(); - } - - if (RealmObjectClassConstructorPrototype) { - RealmObjectClassConstructorPrototype = js::Protected(); - } - - JSValueRef value = nullptr; - value = jsc::Object::get_property(ctx, globalObject, "Object"); - JSObjectRef objectClass = jsc::Value::to_object(ctx, value); - - value = jsc::Object::get_property(ctx, objectClass, "defineProperty"); - ObjectDefineProperty = js::Protected(ctx, Value::to_object(ctx, value)); - - value = jsc::Object::get_property(ctx, globalObject, "Function"); - JSObjectRef globalFunction = jsc::Value::to_object(ctx, value); - value = jsc::Object::get_property(ctx, globalFunction, "prototype"); - FunctionPrototype = js::Protected(ctx, Value::to_object(ctx, value)); - - js::flush_ui_queue = flushUiQueue; -} - -template -using ClassDefinition = js::ClassDefinition; - -using ConstructorType = js::ConstructorType; -using ArgumentsMethodType = js::ArgumentsMethodType; -using Arguments = js::Arguments; -using PropertyType = js::PropertyType; -using IndexPropertyType = js::IndexPropertyType; -using StringPropertyType = js::StringPropertyType; -using MethodMap = js::MethodMap; -using PropertyMap = js::PropertyMap; - -struct SchemaObjectType { - JSObjectRef constructor; -}; - -template -class ObjectWrap { - using Internal = typename ClassType::Internal; - using ParentClassType = typename ClassType::Parent; - -public: - static JSObjectRef create_instance(JSContextRef ctx, Internal* internal = nullptr) - { - return JSObjectMake(ctx, get_class(), new ObjectWrap(internal)); - } - - static JSObjectRef create_instance_by_schema(JSContextRef ctx, const realm::ObjectSchema& schema, - typename ClassType::Internal* internal = nullptr) - { - JSObjectRef constructor = nullptr; - return create_instance_by_schema(ctx, constructor, schema, internal); - } - static JSObjectRef create_instance_by_schema(JSContextRef ctx, JSObjectRef& constructor, - const realm::ObjectSchema& schema, - typename ClassType::Internal* internal = nullptr); - - static JSObjectRef create_constructor(JSContextRef ctx) - { - bool isRealmObjectClass = std::is_same>::value; - if (isRealmObjectClass) { - if (RealmObjectClassConstructor) { - return RealmObjectClassConstructor; - } - - JSObjectRef constructor = JSObjectMake(ctx, get_constructor_class(ctx), nullptr); - RealmObjectClassConstructor = js::Protected(ctx, constructor); - - JSValueRef value = Object::get_property(ctx, RealmObjectClassConstructor, "prototype"); - RealmObjectClassConstructorPrototype = js::Protected(ctx, Value::to_object(ctx, value)); - - return RealmObjectClassConstructor; - } - - return JSObjectMake(ctx, get_constructor_class(ctx), nullptr); - } - - static JSClassRef get_class() - { - if (m_Class != nullptr) { - return m_Class; - } - - JSClassRef js_class = create_class(); - m_Class = JSClassRetain(js_class); - return m_Class; - } - - static JSClassRef get_constructor_class(JSContextRef ctx) - { - if (m_constructorClass != nullptr) { - return m_constructorClass; - } - - JSClassRef js_class = create_constructor_class(); - m_constructorClass = JSClassRetain(js_class); - return m_constructorClass; - } - - static bool has_instance(JSContextRef ctx, JSValueRef value); - - ObjectWrap& operator=(Internal* object) - { - if (m_object.get() != object) { - m_object = std::unique_ptr(object); - } - return *this; - } - - static Internal* get_internal(JSContextRef ctx, const JSObjectRef& object); - - static void on_context_destroy(JSContextRef ctx, std::string realmPath); - -private: - static ClassType s_class; - static std::unordered_map*> s_schemaObjectTypes; - - std::unique_ptr m_object; - - static JSClassRef m_constructorClass; - static JSClassRef m_Class; - static JSClassRef m_internalValueClass; - static JSClassRef m_getterAccessorClass; - static JSClassRef m_setterAccessorClass; - static JSClassRef m_NativePropertyGetterClass; - - ObjectWrap(Internal* object = nullptr) - : m_object(object) - { - } - - static JSClassRef create_constructor_class(); - static JSClassRef create_class(); - - static std::vector get_methods(const MethodMap&); - static std::vector get_properties(const PropertyMap&); - - static JSValueRef call(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*); - static JSObjectRef construct(JSContextRef, JSObjectRef, size_t, const JSValueRef[], JSValueRef*); - static void initialize_constructor(JSContextRef, JSObjectRef); - static void finalize(JSObjectRef); - static void get_property_names(JSContextRef, JSObjectRef, JSPropertyNameAccumulatorRef); - static JSValueRef get_property(JSContextRef, JSObjectRef, JSStringRef, JSValueRef*); - static bool set_property(JSContextRef, JSObjectRef, JSStringRef, JSValueRef, JSValueRef*); - - static bool has_instance(JSContextRef ctx, JSObjectRef constructor, JSValueRef value, JSValueRef* exception); - - static bool set_readonly_property(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef value, - JSValueRef* exception) - { - *exception = Exception::value(ctx, std::string("Cannot assign to read only property '") + - std::string(String(property)) + "'"); - return false; - } - - static void set_internal_property(JSContextRef ctx, JSObjectRef& instance, - typename ClassType::Internal* internal); - - static void define_schema_properties(JSContextRef ctx, JSObjectRef constructorPrototype, - const realm::ObjectSchema& schema, bool redefine); - static void define_accessor_for_schema_property(JSContextRef ctx, JSObjectRef& target, jsc::String* name); - static void define_native_property_accessor(JSContextRef ctx, JSObjectRef& target, jsc::String* name, - JSObjectGetPropertyCallback getCallback); - static JSValueRef native_property_getter_callback(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject, - size_t argumentCount, const JSValueRef arguments[], - JSValueRef* exception); - static JSValueRef accessor_getter(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, - const JSValueRef arguments[], JSValueRef* exception); - static JSValueRef accessor_setter(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, - const JSValueRef arguments[], JSValueRef* exception); -}; - -template <> -class ObjectWrap { -public: - using Internal = void; - - static JSClassRef get_class() - { - return nullptr; - } -}; - -// The static class variable must be defined as well. -template -ClassType ObjectWrap::s_class; - -template -std::unordered_map*> - ObjectWrap::s_schemaObjectTypes; - -template -JSClassRef ObjectWrap::m_getterAccessorClass = nullptr; - -template -JSClassRef ObjectWrap::m_setterAccessorClass = nullptr; - -template -JSClassRef ObjectWrap::m_NativePropertyGetterClass = nullptr; - -template -JSClassRef ObjectWrap::m_internalValueClass = nullptr; - -template -JSClassRef ObjectWrap::m_Class = nullptr; - -template -JSClassRef ObjectWrap::m_constructorClass = nullptr; - -template -inline JSClassRef ObjectWrap::create_class() -{ - JSClassDefinition definition = kJSClassDefinitionEmpty; - std::vector methods; - std::vector properties; - - definition.parentClass = ObjectWrap::get_class(); - definition.className = s_class.name.c_str(); - definition.finalize = finalize; - - if (!s_class.methods.empty()) { - methods = get_methods(s_class.methods); - definition.staticFunctions = methods.data(); - } - if (!s_class.properties.empty()) { - properties = get_properties(s_class.properties); - definition.staticValues = properties.data(); - } - - if (s_class.index_accessor.getter || s_class.string_accessor.getter) { - definition.getProperty = get_property; - definition.setProperty = set_property; - } - else if (s_class.index_accessor.setter || s_class.string_accessor.setter) { - definition.setProperty = set_property; - } - - if (s_class.index_accessor.getter || s_class.string_accessor.enumerator) { - definition.getPropertyNames = get_property_names; - } - - return JSClassCreate(&definition); -} - -template -inline JSClassRef ObjectWrap::create_constructor_class() -{ - JSClassDefinition definition = kJSClassDefinitionEmpty; - std::vector methods; - std::vector properties; - - definition.attributes = kJSClassAttributeNoAutomaticPrototype; - definition.className = "Function"; - definition.initialize = initialize_constructor; - definition.hasInstance = has_instance; - - // This must be set for `typeof constructor` to be 'function'. - definition.callAsFunction = call; - - if (reinterpret_cast(s_class.constructor)) { - definition.callAsConstructor = construct; - } - if (!s_class.static_methods.empty()) { - methods = get_methods(s_class.static_methods); - definition.staticFunctions = methods.data(); - } - if (!s_class.static_properties.empty()) { - properties = get_properties(s_class.static_properties); - definition.staticValues = properties.data(); - } - - bool isRealmObjectClass = std::is_same>::value; - if (isRealmObjectClass) { - if (m_internalValueClass == nullptr) { - JSClassDefinition internalValueClassDefinition = kJSClassDefinitionEmpty; - internalValueClassDefinition.className = "Internal"; - internalValueClassDefinition.finalize = finalize; - m_internalValueClass = JSClassCreate(&internalValueClassDefinition); - m_internalValueClass = JSClassRetain(m_internalValueClass); - } - - if (m_getterAccessorClass == nullptr) { - JSClassDefinition definition = kJSClassDefinitionEmpty; - definition.callAsFunction = accessor_getter; - m_getterAccessorClass = JSClassCreate(&definition); - m_getterAccessorClass = JSClassRetain(m_getterAccessorClass); - } - - if (m_setterAccessorClass == nullptr) { - JSClassDefinition definition = kJSClassDefinitionEmpty; - definition.callAsFunction = accessor_setter; - m_setterAccessorClass = JSClassCreate(&definition); - m_setterAccessorClass = JSClassRetain(m_setterAccessorClass); - } - - if (m_NativePropertyGetterClass == nullptr) { - JSClassDefinition definition = kJSClassDefinitionEmpty; - definition.callAsFunction = native_property_getter_callback; - m_NativePropertyGetterClass = JSClassCreate(&definition); - m_NativePropertyGetterClass = JSClassRetain(m_NativePropertyGetterClass); - } - } - - return JSClassCreate(&definition); -} - -template -inline std::vector ObjectWrap::get_methods(const MethodMap& methods) -{ - std::vector functions; - functions.reserve(methods.size() + 1); - - JSPropertyAttributes attributes = - kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete; - size_t index = 0; - - for (auto& pair : methods) { - functions[index++] = {pair.first.c_str(), pair.second, attributes}; - } - - functions[index] = {0}; - return functions; -} - -template -inline std::vector ObjectWrap::get_properties(const PropertyMap& properties) -{ - std::vector values; - values.reserve(properties.size() + 1); - - JSPropertyAttributes attributes = kJSPropertyAttributeDontEnum | kJSPropertyAttributeDontDelete; - size_t index = 0; - - for (auto& pair : properties) { - auto& prop = pair.second; - values[index++] = {pair.first.c_str(), prop.getter, prop.setter ?: set_readonly_property, attributes}; - } - - values[index] = {0}; - return values; -} - -template -inline void ObjectWrap::on_context_destroy(JSContextRef ctx, std::string realmPath) -{ - std::unordered_map* schemaObjects = nullptr; - if (!s_schemaObjectTypes.count(realmPath)) { - return; - } - - schemaObjects = s_schemaObjectTypes.at(realmPath); - for (auto it = schemaObjects->begin(); it != schemaObjects->end(); ++it) { - JSValueUnprotect(ctx, it->second->constructor); - it->second->constructor = nullptr; - SchemaObjectType* schemaObjecttype = it->second; - delete schemaObjecttype; - } - s_schemaObjectTypes.erase(realmPath); - - delete schemaObjects; -} - -template -inline JSValueRef ObjectWrap::accessor_getter(JSContextRef ctx, JSObjectRef function, - JSObjectRef this_object, size_t argc, - const JSValueRef arguments[], JSValueRef* exception) -{ - bool isRealmObjectClass = std::is_same>::value; - REALM_ASSERT(isRealmObjectClass); - - void* data = JSObjectGetPrivate(function); - jsc::String* propertyName = (jsc::String*)data; -#ifdef DEBUG - std::string debugName = *propertyName; -#endif - - return s_class.string_accessor.getter(ctx, this_object, *propertyName, exception); -} - -template -inline JSValueRef ObjectWrap::accessor_setter(JSContextRef ctx, JSObjectRef function, - JSObjectRef this_object, size_t argc, - const JSValueRef arguments[], JSValueRef* exception) -{ - bool isRealmObjectClass = std::is_same>::value; - REALM_ASSERT(isRealmObjectClass); - - void* data = JSObjectGetPrivate(function); - jsc::String* propertyName = (jsc::String*)data; -#ifdef DEBUG - std::string debugName = *propertyName; -#endif - - bool result = s_class.string_accessor.setter(ctx, this_object, *propertyName, arguments[0], exception); - return Value::from_boolean(ctx, result); -} - -template -inline JSValueRef ObjectWrap::native_property_getter_callback(JSContextRef ctx, JSObjectRef function, - JSObjectRef thisObject, size_t argumentCount, - const JSValueRef arguments[], - JSValueRef* exception) -{ - bool isRealmObjectClass = std::is_same>::value; - REALM_ASSERT(isRealmObjectClass); - - JSValueRef error = nullptr; - - void* data = JSObjectGetPrivate(function); - JSValueRef value = JSObjectGetProperty(ctx, function, JSStringCreateWithUTF8CString("propertyName"), &error); - if (error) { - *exception = error; - return nullptr; - } - jsc::String propertyName = Value::to_string(ctx, value); -#ifdef DEBUG - std::string debugName = propertyName; -#endif - - JSObjectGetPropertyCallback getterCallback = (JSObjectGetPropertyCallback)data; - - JSValueRef result = getterCallback(ctx, thisObject, propertyName, &error); - if (error) { - *exception = error; - return nullptr; - } - - return result; -} - -template -void ObjectWrap::define_accessor_for_schema_property(JSContextRef ctx, JSObjectRef& target, - jsc::String* name) -{ - JSObjectRef descriptor = Object::create_empty(ctx); - JSValueRef exception = nullptr; - - // create an object with attached function callback. This is to be able to set private data on the function. - // Use this function as the 'get' function in the property descriptor - // set the property name as private data. In the future this could be the realm::Property or the table index to - // speed up the get operation - JSObjectRef getter = JSObjectMake(ctx, m_getterAccessorClass, name); - JSObjectSetPrototype(ctx, getter, FunctionPrototype); - - JSObjectSetProperty(ctx, descriptor, JSStringCreateWithUTF8CString("get"), getter, kJSPropertyAttributeReadOnly, - &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - JSObjectRef setter = JSObjectMake(ctx, m_setterAccessorClass, name); - JSObjectSetProperty(ctx, descriptor, JSStringCreateWithUTF8CString("set"), setter, kJSPropertyAttributeReadOnly, - &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - JSObjectSetProperty(ctx, descriptor, JSStringCreateWithUTF8CString("enumerable"), Value::from_boolean(ctx, true), - kJSPropertyAttributeReadOnly, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - // call Object.defineProperty - JSValueRef arguments[] = {target, Value::from_string(ctx, *name), descriptor}; - Function::call(ctx, ObjectDefineProperty, nullptr /*this*/, 3, arguments); -} - -template -void ObjectWrap::define_native_property_accessor(JSContextRef ctx, JSObjectRef& target, jsc::String* name, - JSObjectGetPropertyCallback getCallback) -{ - - JSObjectRef descriptor = Object::create_empty(ctx); - JSValueRef exception = nullptr; - - JSObjectRef getter = JSObjectMake(ctx, m_NativePropertyGetterClass, (void*)getCallback); - JSObjectSetPrototype(ctx, getter, FunctionPrototype); - JSObjectSetProperty(ctx, getter, JSStringCreateWithUTF8CString("propertyName"), Value::from_string(ctx, *name), - kJSPropertyAttributeReadOnly, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - JSObjectSetProperty(ctx, descriptor, JSStringCreateWithUTF8CString("get"), getter, kJSPropertyAttributeReadOnly, - &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - - // call Object.defineProperty - JSValueRef arguments[] = {target, Value::from_string(ctx, *name), descriptor}; - Function::call(ctx, ObjectDefineProperty, nullptr /*this*/, 3, arguments); -} - -// A cache for property names. The pair is property name and a node::String* to the same string representation. -// The cache is persisted throughout the process life time to preseve property names between constructor cache -// invalidations (on_destory_context is called) Since RealmObjectClass instances may be used after context is -// destroyed, their property names should be valid -static std::unordered_map propertyNamesCache; - -static jsc::String* get_cached_property_name(const std::string& name) -{ - if (propertyNamesCache.count(name)) { - jsc::String* cachedName = propertyNamesCache.at(name); - return cachedName; - } - - jsc::String* result = new jsc::String(name); - propertyNamesCache.emplace(name, result); - return result; -} - -static void define_function_property(JSContextRef ctx, JSObjectRef& target, const char* name, - const JSObjectCallAsFunctionCallback& callback) -{ - JSObjectRef descriptor = Object::create_empty(ctx); - - JSObjectRef functionValue = JSObjectMakeFunctionWithCallback(ctx, JSStringCreateWithUTF8CString(name), callback); - - JSValueRef exception = nullptr; - JSObjectSetProperty(ctx, descriptor, JSStringCreateWithUTF8CString("value"), functionValue, - kJSPropertyAttributeNone, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - JSObjectSetProperty(ctx, descriptor, JSStringCreateWithUTF8CString("writable"), Value::from_boolean(ctx, true), - kJSPropertyAttributeNone, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - JSObjectSetProperty(ctx, descriptor, JSStringCreateWithUTF8CString("configurable"), - Value::from_boolean(ctx, true), kJSPropertyAttributeNone, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - JSValueRef arguments[] = {target, Value::from_string(ctx, name), descriptor}; - Function::call(ctx, ObjectDefineProperty, nullptr /*this*/, 3, arguments); -} - -static inline void remove_schema_object(JSContextRef ctx, - std::unordered_map* schemaObjects, - const std::string& schemaName) -{ - bool schemaExists = schemaObjects->count(schemaName); - if (!schemaExists) { - return; - } - - SchemaObjectType* schemaObjectType = schemaObjects->at(schemaName); - schemaObjects->erase(schemaName); - JSValueUnprotect(ctx, schemaObjectType->constructor); - delete schemaObjectType; -} - -template -typename ClassType::Internal* ObjectWrap::get_internal(JSContextRef ctx, const JSObjectRef& object) -{ - JSObjectRef instance = object; - bool isRealmObjectClass = std::is_same>::value; - if (isRealmObjectClass) { - const jsc::String* externalName = get_cached_property_name("_external"); - JSValueRef value = Object::get_property(ctx, object, *externalName); - if (Value::is_undefined(ctx, value)) { - return nullptr; - } - - instance = Value::to_object(ctx, value); - } - - ObjectWrap* realmObjectInstance = static_cast*>(JSObjectGetPrivate(instance)); - return realmObjectInstance->m_object.get(); -} - -template -void ObjectWrap::set_internal_property(JSContextRef ctx, JSObjectRef& instance, - typename ClassType::Internal* internal) -{ - // create a JS object that has a finializer to delete the internal reference - JSObjectRef internalObject = JSObjectMake(ctx, m_internalValueClass, new ObjectWrap(internal)); - - const jsc::String* externalName = get_cached_property_name("_external"); - auto attributes = realm::js::PropertyAttributes::ReadOnly | realm::js::PropertyAttributes::DontDelete | - realm::js::PropertyAttributes::DontEnum; - Object::set_property(ctx, instance, *externalName, internalObject, attributes); -} - -static inline JSObjectRef try_get_prototype(JSContextRef ctx, JSObjectRef object) -{ - JSValueRef exception = nullptr; - JSValueRef protoValue = JSObjectGetPrototype(ctx, object); - if (JSValueIsNull(ctx, protoValue)) { - return nullptr; - } - - JSObjectRef proto = JSValueToObject(ctx, protoValue, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - return proto; -} - -// This is called from realm's abstraction layer and by JSValueIsInstanceOfConstructor -template -bool ObjectWrap::has_instance(JSContextRef ctx, JSValueRef value) -{ - bool isRealmObjectClass = std::is_same>::value; - if (isRealmObjectClass) { - // Can't use JSValueIsObjectOfClass for RealmObjectClass instances created from a user defined constructor in - // the schema. Can't use JSValueIsInstanceOfConstructor with the RealmObjectClass constructor since it will - // recursively call this method again Check if the object has RealmObjectClassConstructorPrototype in its - // proto chain (the definition of JS 'instanceof') - if (!JSValueIsObject(ctx, value)) { - return false; - } - - JSValueRef error = nullptr; - JSObjectRef object = JSValueToObject(ctx, value, &error); - if (error) { - // do not throw exceptions in 'instanceof' calls - return false; - } - - // search for RealmObjectClassConstructor on the prototype chain of the object - JSObjectRef proto = try_get_prototype(ctx, object); - while (proto != nullptr && !JSValueIsNull(ctx, proto)) { - if (JSValueIsStrictEqual(ctx, proto, RealmObjectClassConstructorPrototype)) { - return true; - } - - proto = try_get_prototype(ctx, proto); - } - - // handle RealmObjects using user defined ctors without extending RealmObject. - // In this case we just check for existing internal value to identify RealmObject instances - auto internal = ObjectWrap::get_internal(ctx, object); - if (internal != nullptr) { - return true; - } - - // if there is no RealmObjectClass on the prototype chain and the object does not have existing internal value - // then this is not an RealmObject instance - return false; - } - - return JSValueIsObjectOfClass(ctx, value, get_class()); -} - -// JavaScriptCore calls this private method when doing 'instanceof' from JS -template -bool ObjectWrap::has_instance(JSContextRef ctx, JSObjectRef constructor, JSValueRef value, - JSValueRef* exception) -{ - return has_instance(ctx, value); -} - -template -inline void ObjectWrap::define_schema_properties(JSContextRef ctx, JSObjectRef constructorPrototype, - const realm::ObjectSchema& schema, bool redefine) -{ - // get all properties from the schema - for (auto& property : schema.persisted_properties) { - std::string propName = property.public_name.empty() ? property.name : property.public_name; - if (redefine || - !JSObjectHasProperty(ctx, constructorPrototype, JSStringCreateWithUTF8CString(propName.c_str()))) { - jsc::String* name = get_cached_property_name(propName); - define_accessor_for_schema_property(ctx, constructorPrototype, name); - } - } - - for (auto& property : schema.computed_properties) { - std::string propName = property.public_name.empty() ? property.name : property.public_name; - if (redefine || - !JSObjectHasProperty(ctx, constructorPrototype, JSStringCreateWithUTF8CString(propName.c_str()))) { - jsc::String* name = get_cached_property_name(propName); - define_accessor_for_schema_property(ctx, constructorPrototype, name); - } - } -} - -template -inline JSObjectRef ObjectWrap::create_instance_by_schema(JSContextRef ctx, JSObjectRef& constructor, - const realm::ObjectSchema& schema, - typename ClassType::Internal* internal) -{ - bool isRealmObjectClass = std::is_same>::value; - if (!isRealmObjectClass) { - JSValueRef exception = - jsc::Exception::value(ctx, "Creating instances by schema is supported for RealmObjectClass only"); - throw jsc::Exception(ctx, exception); - } - - if (isRealmObjectClass && !internal) { - JSValueRef exception = jsc::Exception::value( - ctx, "RealmObjectClass requires an internal realm object when creating instances by schema"); - throw jsc::Exception(ctx, exception); - } - - JSObjectRef instance; - JSValueRef value; - - auto config = internal->realm()->config(); - std::string path = config.path; - auto version = internal->realm()->schema_version(); - std::string schemaName = schema.name + ":" + util::to_string(version); - - std::unordered_map* schemaObjects = nullptr; - if (!s_schemaObjectTypes.count(path)) { - schemaObjects = new std::unordered_map(); - s_schemaObjectTypes.emplace(path, schemaObjects); - } - else { - schemaObjects = s_schemaObjectTypes.at(path); - } - - JSObjectRef schemaObjectConstructor; - SchemaObjectType* schemaObjectType; - JSObjectRef constructorPrototype; - - // if we are creating a RealmObject from schema with no user defined constructor - if (constructor == nullptr) { - if (!schemaObjects->count(schemaName)) { - - JSClassDefinition definition = kJSClassDefinitionEmpty; - definition.className = schema.name.c_str(); - JSClassRef schemaClass = JSClassCreate(&definition); - schemaObjectConstructor = JSObjectMakeConstructor(ctx, schemaClass, nullptr); - value = Object::get_property(ctx, schemaObjectConstructor, "prototype"); - constructorPrototype = Value::to_object(ctx, value); - - JSObjectSetPrototype(ctx, constructorPrototype, RealmObjectClassConstructorPrototype); - JSObjectSetPrototype(ctx, schemaObjectConstructor, RealmObjectClassConstructor); - - define_schema_properties(ctx, constructorPrototype, schema, true); - - schemaObjectType = new SchemaObjectType(); - schemaObjects->emplace(schemaName, schemaObjectType); - JSValueProtect(ctx, schemaObjectConstructor); - schemaObjectType->constructor = schemaObjectConstructor; - } - else { - // hot path. The constructor for this schema object is already cached. - schemaObjectType = schemaObjects->at(schemaName); - schemaObjectConstructor = schemaObjectType->constructor; - } - - instance = Function::construct(ctx, schemaObjectConstructor, 0, {}); - - // save the internal object on the instance - set_internal_property(ctx, instance, internal); - - return instance; - } - else { - // creating a RealmObject with user defined constructor - bool schemaExists = schemaObjects->count(schemaName); - if (schemaExists) { - schemaObjectType = schemaObjects->at(schemaName); - schemaObjectConstructor = schemaObjectType->constructor; - - // check if constructors have changed for the same schema object and name - if (!JSValueIsStrictEqual(ctx, schemaObjectConstructor, constructor)) { - schemaExists = false; - remove_schema_object(ctx, schemaObjects, schemaName); - } - } - - // hot path. The constructor for this schema object is already cached. use it and return a new instance - if (schemaExists) { - schemaObjectType = schemaObjects->at(schemaName); - schemaObjectConstructor = schemaObjectType->constructor; - - instance = Function::construct(ctx, schemaObjectConstructor, 0, {}); - set_internal_property(ctx, instance, internal); - return instance; - } - - schemaObjectConstructor = constructor; - value = Object::get_property(ctx, constructor, "prototype"); - constructorPrototype = Value::to_object(ctx, value); - - define_schema_properties(ctx, constructorPrototype, schema, false); - - JSValueRef exception = nullptr; - bool isInstanceOfRealmObjectClass = - JSValueIsInstanceOfConstructor(ctx, constructorPrototype, RealmObjectClassConstructor, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - // Skip if the user defined constructor inherited the RealmObjectClass. All RealmObjectClass members are - // available already. - if (!isInstanceOfRealmObjectClass) { - // setup all RealmObjectClass methods to the prototype of the object - for (auto& pair : s_class.methods) { - // don't redefine if exists - if (!JSObjectHasProperty(ctx, constructorPrototype, - JSStringCreateWithUTF8CString(pair.first.c_str()))) { - define_function_property(ctx, constructorPrototype, pair.first.c_str(), pair.second); - } - } - - for (auto& pair : s_class.properties) { - // don't redefine if exists - if (!JSObjectHasProperty(ctx, constructorPrototype, - JSStringCreateWithUTF8CString(pair.first.c_str()))) { - jsc::String* name = get_cached_property_name(pair.first); - JSObjectGetPropertyCallback getterCallback = pair.second.getter; - define_native_property_accessor(ctx, constructorPrototype, name, getterCallback); - } - } - } - - // create the instance - instance = Function::construct(ctx, schemaObjectConstructor, 0, {}); - bool instanceOfSchemaConstructor = - JSValueIsInstanceOfConstructor(ctx, instance, schemaObjectConstructor, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - if (!instanceOfSchemaConstructor) { - throw jsc::Exception(ctx, "Realm object constructor must not return another value"); - } - - // save the internal object on the instance - set_internal_property(ctx, instance, internal); - - schemaObjectType = new SchemaObjectType(); - schemaObjects->emplace(schemaName, schemaObjectType); - JSValueProtect(ctx, schemaObjectConstructor); - schemaObjectType->constructor = schemaObjectConstructor; - - return instance; - } -} - -template -inline JSValueRef ObjectWrap::call(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, - size_t argc, const JSValueRef arguments[], JSValueRef* exception) -{ - // This should only be called as a super() call in the constructor of a subclass. - if (!has_instance(ctx, this_object)) { - *exception = jsc::Exception::value(ctx, s_class.name + " cannot be called as a function"); - return nullptr; - } - - // Classes without a constructor should still be subclassable. - if (reinterpret_cast(s_class.constructor)) { - jsc::Arguments args{ctx, argc, arguments}; - try { - s_class.constructor(ctx, this_object, args); - } - catch (std::exception& e) { - *exception = jsc::Exception::value(ctx, e); - return nullptr; - } - } - - return JSValueMakeUndefined(ctx); -} - -template -inline JSObjectRef ObjectWrap::construct(JSContextRef ctx, JSObjectRef constructor, size_t argc, - const JSValueRef arguments[], JSValueRef* exception) -{ - if (!reinterpret_cast(s_class.constructor)) { - *exception = jsc::Exception::value(ctx, s_class.name + " is not a constructor"); - return nullptr; - } - - JSObjectRef this_object = create_instance(ctx); - jsc::Arguments args{ctx, argc, arguments}; - try { - s_class.constructor(ctx, this_object, args); - } - catch (std::exception& e) { - *exception = jsc::Exception::value(ctx, e); - return nullptr; - } - return this_object; -} - -template -inline void ObjectWrap::initialize_constructor(JSContextRef ctx, JSObjectRef constructor) -{ - static const String prototype_string = "prototype"; - - // Set the prototype of the constructor to be Function.prototype. - Object::set_prototype(ctx, constructor, - Object::get_prototype(ctx, JSObjectMakeFunctionWithCallback(ctx, nullptr, call))); - - // Set the constructor prototype to be the prototype generated from the instance JSClassRef. - JSObjectRef prototype = - Object::validated_get_object(ctx, JSObjectMakeConstructor(ctx, get_class(), construct), prototype_string); - Object::set_property(ctx, constructor, prototype_string, prototype, js::ReadOnly | js::DontEnum | js::DontDelete); -} - -template -inline void ObjectWrap::finalize(JSObjectRef object) -{ - // This is called for the most derived class before superclasses. - if (auto wrap = static_cast*>(JSObjectGetPrivate(object))) { - delete wrap; - JSObjectSetPrivate(object, nullptr); - } -} - -template -inline void ObjectWrap::get_property_names(JSContextRef ctx, JSObjectRef object, - JSPropertyNameAccumulatorRef accumulator) -{ - if (s_class.index_accessor.getter) { - try { - uint32_t length = Object::validated_get_length(ctx, object); - char string[32]; - for (uint32_t i = 0; i < length; i++) { - sprintf(string, "%u", i); - JSPropertyNameAccumulatorAddName(accumulator, jsc::String(string)); - } - } - catch (std::exception&) { - // Enumerating properties should never throw an exception into JS. - } - } - if (auto string_enumerator = s_class.string_accessor.enumerator) { - string_enumerator(ctx, object, accumulator); - } -} - -static inline bool try_get_int(JSStringRef property, int64_t& value) -{ - value = 0; - auto str = JSStringGetCharactersPtr(property); - auto end = str + JSStringGetLength(property); - while (str != end && iswspace(*str)) { - ++str; - } - bool negative = false; - if (str != end && *str == '-') { - negative = true; - ++str; - } - while (str != end && *str >= '0' && *str <= '9') { - if (util::int_multiply_with_overflow_detect(value, 10)) { - return false; - } - value += *str - '0'; - ++str; - } - if (negative) { - value *= -1; - } - return str == end; -} - -template -inline JSValueRef ObjectWrap::get_property(JSContextRef ctx, JSObjectRef object, JSStringRef property, - JSValueRef* exception) -{ - if (JSStringGetLength(property) == 0) { - return Value::from_undefined(ctx); - } - - if (auto index_getter = s_class.index_accessor.getter) { - int64_t num; - if (try_get_int(property, num)) { - uint32_t index; - if (num < 0 || util::int_cast_with_overflow_detect(num, index)) { - // Out-of-bounds index getters should just return undefined in JS. - return Value::from_undefined(ctx); - } - - return index_getter(ctx, object, index, exception); - } - } - - if (auto string_getter = s_class.string_accessor.getter) { - return string_getter(ctx, object, property, exception); - } - - return nullptr; -} - -template -inline bool ObjectWrap::set_property(JSContextRef ctx, JSObjectRef object, JSStringRef property, - JSValueRef value, JSValueRef* exception) -{ - if (JSStringGetLength(property) == 0) { - return false; - } - - auto index_setter = s_class.index_accessor.setter; - - if (index_setter || s_class.index_accessor.getter) { - int64_t num; - if (try_get_int(property, num)) { - if (num < 0) { - *exception = Exception::value(ctx, util::format("Index %1 cannot be less than zero.", num)); - return false; - } - - int32_t index; - if (util::int_cast_with_overflow_detect(num, index)) { - *exception = Exception::value(ctx, util::format("Index %1 cannot be greater than %2.", num, - std::numeric_limits::max())); - return false; - } - - if (index_setter) { - return index_setter(ctx, object, index, value, exception); - } - - *exception = Exception::value(ctx, util::format("Cannot assign to read only index %1", index)); - return false; - } - } - - if (auto string_setter = s_class.string_accessor.setter) { - return string_setter(ctx, object, property, value, exception); - } - - return false; -} - -} // namespace jsc - -namespace js { - -template -class ObjectWrap : public jsc::ObjectWrap {}; - -template -JSValueRef wrap(JSContextRef ctx, JSObjectRef, JSObjectRef this_object, size_t argc, const JSValueRef arguments[], - JSValueRef* exception) -{ - jsc::Arguments args{ctx, argc, arguments}; - jsc::ReturnValue return_value(ctx); - try { - F(ctx, this_object, args, return_value); - return return_value; - } - catch (std::exception& e) { - *exception = jsc::Exception::value(ctx, e); - return nullptr; - } -} - -template -JSValueRef wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef* exception) -{ - jsc::ReturnValue return_value(ctx); - try { - F(ctx, object, return_value); - return return_value; - } - catch (std::exception& e) { - *exception = jsc::Exception::value(ctx, e); - return nullptr; - } -} - -template -bool wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef value, JSValueRef* exception) -{ - try { - F(ctx, object, value); - return true; - } - catch (std::exception& e) { - *exception = jsc::Exception::value(ctx, e); - return false; - } -} - -template -JSValueRef wrap(JSContextRef ctx, JSObjectRef object, uint32_t index, JSValueRef* exception) -{ - jsc::ReturnValue return_value(ctx); - try { - F(ctx, object, index, return_value); - return return_value; - } - catch (std::out_of_range&) { - // Out-of-bounds index getters should just return undefined in JS. - return jsc::Value::from_undefined(ctx); - } - catch (std::exception& e) { - *exception = jsc::Exception::value(ctx, e); - return nullptr; - } -} - -template -bool wrap(JSContextRef ctx, JSObjectRef object, uint32_t index, JSValueRef value, JSValueRef* exception) -{ - try { - return F(ctx, object, index, value); - } - catch (std::exception& e) { - *exception = jsc::Exception::value(ctx, e); - return false; - } -} - -template -JSValueRef wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef* exception) -{ - jsc::ReturnValue return_value(ctx); - try { - F(ctx, object, property, return_value); - return return_value; - } - catch (std::exception& e) { - *exception = jsc::Exception::value(ctx, e); - return nullptr; - } -} - -template -bool wrap(JSContextRef ctx, JSObjectRef object, JSStringRef property, JSValueRef value, JSValueRef* exception) -{ - try { - return F(ctx, object, property, value); - } - catch (std::exception& e) { - *exception = jsc::Exception::value(ctx, e); - return false; - } -} - -template -void wrap(JSContextRef ctx, JSObjectRef object, JSPropertyNameAccumulatorRef accumulator) -{ - auto names = F(ctx, object); - for (auto& name : names) { - JSPropertyNameAccumulatorAddName(accumulator, name); - } -} - -} // namespace js -} // namespace realm diff --git a/src/jsc/jsc_exception.hpp b/src/jsc/jsc_exception.hpp deleted file mode 100644 index 51757ecce8..0000000000 --- a/src/jsc/jsc_exception.hpp +++ /dev/null @@ -1,34 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "jsc_types.hpp" - -namespace realm { -namespace js { - -template <> -inline JSValueRef jsc::Exception::value(JSContextRef ctx, const std::string& message) -{ - JSValueRef value = jsc::Value::from_string(ctx, message); - return JSObjectMakeError(ctx, 1, &value, NULL); -} - -} // namespace js -} // namespace realm diff --git a/src/jsc/jsc_function.hpp b/src/jsc/jsc_function.hpp deleted file mode 100644 index cf3caf345a..0000000000 --- a/src/jsc/jsc_function.hpp +++ /dev/null @@ -1,65 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "jsc_types.hpp" -#include "jsc_externs.hpp" - -namespace realm { -namespace js { - -template <> -inline JSValueRef jsc::Function::call(JSContextRef ctx, const JSObjectRef& function, const JSObjectRef& this_object, - size_t argc, const JSValueRef arguments[]) -{ - JSValueRef exception = nullptr; - JSValueRef result = JSObjectCallAsFunction(ctx, function, this_object, argc, arguments, &exception); - - flush_ui_queue(); - - if (exception) { - throw jsc::Exception(ctx, exception); - } - return result; -} - -template <> -inline JSValueRef jsc::Function::callback(JSContextRef ctx, const JSObjectRef& function, - const JSObjectRef& this_object, size_t argc, const JSValueRef arguments[]) -{ - return jsc::Function::call(ctx, function, this_object, argc, arguments); -} - -template <> -inline JSObjectRef jsc::Function::construct(JSContextRef ctx, const JSObjectRef& function, size_t argc, - const JSValueRef arguments[]) -{ - JSValueRef exception = nullptr; - JSObjectRef result = JSObjectCallAsConstructor(ctx, function, argc, arguments, &exception); - - flush_ui_queue(); - - if (exception) { - throw jsc::Exception(ctx, exception); - } - return result; -} - -} // namespace js -} // namespace realm diff --git a/src/jsc/jsc_object.hpp b/src/jsc/jsc_object.hpp deleted file mode 100644 index 319d280aa0..0000000000 --- a/src/jsc/jsc_object.hpp +++ /dev/null @@ -1,195 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "jsc_types.hpp" - -namespace realm { -namespace js { - -template <> -inline JSValueRef jsc::Object::get_property(JSContextRef ctx, const JSObjectRef& object, const jsc::String& key) -{ - JSValueRef exception = nullptr; - JSValueRef value = JSObjectGetProperty(ctx, object, key, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - return value; -} - -template <> -inline JSValueRef jsc::Object::get_property(JSContextRef ctx, const JSObjectRef& object, StringData key) -{ - return get_property(ctx, object, jsc::String(key)); -} - -template <> -inline JSValueRef jsc::Object::get_property(JSContextRef ctx, const JSObjectRef& object, uint32_t index) -{ - JSValueRef exception = nullptr; - JSValueRef value = JSObjectGetPropertyAtIndex(ctx, object, index, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - return value; -} - -template <> -inline void jsc::Object::set_property(JSContextRef ctx, JSObjectRef& object, const jsc::String& key, - const JSValueRef& value, PropertyAttributes attributes) -{ - JSValueRef exception = nullptr; - JSObjectSetProperty(ctx, object, key, value, attributes << 1, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } -} - -template <> -inline void jsc::Object::set_property(JSContextRef ctx, JSObjectRef& object, uint32_t index, const JSValueRef& value) -{ - JSValueRef exception = nullptr; - JSObjectSetPropertyAtIndex(ctx, object, index, value, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } -} - -template <> -inline std::vector jsc::Object::get_property_names(JSContextRef ctx, const JSObjectRef& object) -{ - JSPropertyNameArrayRef property_names = JSObjectCopyPropertyNames(ctx, object); - size_t property_count = JSPropertyNameArrayGetCount(property_names); - - std::vector names; - names.reserve(property_count); - - for (size_t i = 0; i < property_count; i++) { - names.push_back(JSPropertyNameArrayGetNameAtIndex(property_names, i)); - } - - JSPropertyNameArrayRelease(property_names); - return names; -} - -template <> -inline JSValueRef jsc::Object::get_prototype(JSContextRef ctx, const JSObjectRef& object) -{ - return JSObjectGetPrototype(ctx, object); -} - -template <> -inline void jsc::Object::set_prototype(JSContextRef ctx, const JSObjectRef& object, const JSValueRef& prototype) -{ - JSObjectSetPrototype(ctx, object, prototype); -} - -template <> -inline JSObjectRef jsc::Object::create_empty(JSContextRef ctx) -{ - return JSObjectMake(ctx, nullptr, nullptr); -} - -template <> -inline JSObjectRef jsc::Object::create_array(JSContextRef ctx, uint32_t length, const JSValueRef values[]) -{ - JSValueRef exception = nullptr; - JSObjectRef array = JSObjectMakeArray(ctx, length, values, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - return array; -} - -template <> -inline JSObjectRef jsc::Object::create_date(JSContextRef ctx, double time) -{ - JSValueRef number = jsc::Value::from_number(ctx, time); - return JSObjectMakeDate(ctx, 1, &number, nullptr); -} - -template <> -template -inline JSObjectRef jsc::Object::create_instance(JSContextRef ctx, typename ClassType::Internal* internal) -{ - return jsc::ObjectWrap::create_instance(ctx, internal); -} - -template <> -template -inline JSObjectRef jsc::Object::create_instance_by_schema(JSContextRef ctx, JSObjectRef& constructor, - const realm::ObjectSchema& schema, - typename ClassType::Internal* internal) -{ - return jsc::ObjectWrap::create_instance_by_schema(ctx, constructor, schema, internal); -} - -template <> -template -inline JSObjectRef jsc::Object::create_instance_by_schema(JSContextRef ctx, const realm::ObjectSchema& schema, - typename ClassType::Internal* internal) -{ - return jsc::ObjectWrap::create_instance_by_schema(ctx, schema, internal); -} - -template -inline void on_context_destroy(JSContextRef ctx, std::string realmPath) -{ - jsc::ObjectWrap::on_context_destroy(ctx, realmPath); -} - -template <> -template -inline bool jsc::Object::is_instance(JSContextRef ctx, const JSObjectRef& object) -{ - return jsc::ObjectWrap::has_instance(ctx, object); -} - -template <> -template -inline typename ClassType::Internal* jsc::Object::get_internal(JSContextRef ctx, const JSObjectRef& object) -{ - return jsc::ObjectWrap::get_internal(ctx, object); -} - -template <> -template -inline void jsc::Object::set_internal(JSContextRef ctx, const JSObjectRef& object, typename ClassType::Internal* ptr) -{ - auto wrap = static_cast*>(JSObjectGetPrivate(object)); - *wrap = ptr; -} - -template <> -inline void jsc::Object::set_global(JSContextRef ctx, const jsc::String& key, const JSValueRef& value) -{ - JSObjectRef global_object = JSContextGetGlobalObject(ctx); - jsc::Object::set_property(ctx, global_object, key, value, js::ReadOnly | js::DontEnum | js::DontDelete); -} - -template <> -inline JSValueRef jsc::Object::get_global(JSContextRef ctx, const jsc::String& key) -{ - JSObjectRef global_object = JSContextGetGlobalObject(ctx); - return jsc::Object::get_property(ctx, global_object, key); -} - -} // namespace js -} // namespace realm diff --git a/src/jsc/jsc_protected.hpp b/src/jsc/jsc_protected.hpp deleted file mode 100644 index 2e345b9403..0000000000 --- a/src/jsc/jsc_protected.hpp +++ /dev/null @@ -1,174 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "jsc_types.hpp" - -namespace realm { -namespace js { - -template <> -class Protected { - JSGlobalContextRef m_context; - -public: - Protected() - : m_context(nullptr) - { - } - Protected(const Protected& other) - : Protected(other.m_context) - { - } - Protected(Protected&& other) - : m_context(other.m_context) - { - other.m_context = nullptr; - } - explicit Protected(JSGlobalContextRef ctx) - : m_context(ctx) - { - JSGlobalContextRetain(m_context); - } - ~Protected() - { - if (m_context) { - JSGlobalContextRelease(m_context); - } - } - operator JSGlobalContextRef() const - { - return m_context; - } - operator bool() const - { - return m_context != nullptr; - } - - struct Comparator { - bool operator()(const Protected& a, const Protected& b) const - { - return a.m_context == b.m_context; - } - }; -}; - -template <> -class Protected { -protected: - JSGlobalContextRef m_context = nullptr; - JSValueRef m_value = nullptr; - -public: - Protected() {} - - Protected(const Protected& other) - : Protected(other.m_context, other.m_value) - { - } - - Protected(Protected&& other) - : m_context(other.m_context) - , m_value(other.m_value) - { - other.m_context = nullptr; - other.m_value = nullptr; - } - - Protected(JSContextRef ctx, JSValueRef value) - : m_context(JSContextGetGlobalContext(ctx)) - , m_value(value) - { - JSGlobalContextRetain(m_context); - JSValueProtect(m_context, m_value); - } - - ~Protected() - { - if (m_value) { - JSValueUnprotect(m_context, m_value); - } - - if (m_context) { - JSGlobalContextRelease(m_context); - } - } - operator JSValueRef() const - { - return m_value; - } - operator bool() const - { - return m_value != nullptr; - } - - struct Comparator { - bool operator()(const Protected& a, const Protected& b) const - { - if (a.m_context != b.m_context) { - return false; - } - return JSValueIsStrictEqual(a.m_context, a.m_value, b.m_value); - } - }; - - Protected& operator=(Protected other) - { - std::swap(m_context, other.m_context); - std::swap(m_value, other.m_value); - return *this; - } -}; - -template <> -class Protected : public Protected { -public: - Protected() - : Protected() - { - } - Protected(const Protected& other) - : Protected(other) - { - } - Protected(Protected&& other) - : Protected(std::move(other)) - { - } - Protected(JSContextRef ctx, JSObjectRef value) - : Protected(ctx, value) - { - } - - operator JSObjectRef() const - { - JSValueRef value = static_cast(*this); - return (JSObjectRef)value; - } - - Protected& operator=(Protected other) - { - std::swap(m_context, other.m_context); - std::swap(m_value, other.m_value); - return *this; - } -}; - -} // namespace js -} // namespace realm diff --git a/src/jsc/jsc_rpc_network_transport.hpp b/src/jsc/jsc_rpc_network_transport.hpp deleted file mode 100644 index f365757610..0000000000 --- a/src/jsc/jsc_rpc_network_transport.hpp +++ /dev/null @@ -1,79 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2020 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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include -#include -#include "js_types.hpp" -#include "js_network_transport.hpp" - -namespace realm { -namespace rpc { - -/* -struct RPCFetchRef { - static JSValueRef value; -}; -*/ - -/** - * Provides an implementation of GenericNetworkTransport for use when the library is loaded in a runtime which doesn't - * provide the APIs required to perform network requests directly. Instead it uses the RPC server to ask the RPC - * client to perform network requests on its behalf. - */ -struct RPCNetworkTransport : public app::GenericNetworkTransport { - using T = jsc::Types; - using ContextType = typename T::Context; - using FunctionType = typename T::Function; - using ObjectType = typename T::Object; - using ValueType = typename T::Value; - using String = js::String; - using Object = js::Object; - using Value = js::Value; - using Function = js::Function; - - using SendRequestHandler = void(ContextType m_ctx, app::Request&& request, - util::UniqueFunction&& completion_callback); - - static inline js::Protected fetch_function; - - RPCNetworkTransport(ContextType ctx) - : m_ctx(ctx) - { - } - - void send_request_to_server(app::Request&& request, - util::UniqueFunction&& completion_callback) override - { - // Build up a JS request object - auto request_object = js::JavaScriptNetworkTransport::makeRequest(m_ctx, request); - // Ask the RPC layer to enqueue a call to the client-side fetch function - Function::call(m_ctx, fetch_function, nullptr, - { - request_object, - js::ResponseHandlerClass::create_instance(m_ctx, std::move(completion_callback)), - }); - } - -private: - ContextType m_ctx; -}; - -} // namespace rpc -} // namespace realm diff --git a/src/jsc/jsc_string.hpp b/src/jsc/jsc_string.hpp deleted file mode 100644 index f448096f17..0000000000 --- a/src/jsc/jsc_string.hpp +++ /dev/null @@ -1,90 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "jsc_types.hpp" - -namespace realm { -namespace js { - -template <> -class String { - using StringType = String; - - JSStringRef m_str; - -public: - static bson::Bson to_bson(StringType stringified_ejson) - { - return bson::parse(std::string(stringified_ejson)); - } - - static String from_bson(const bson::Bson& bson) - { - return String(bson.to_string()); - } - - String(const char* s) - : m_str(JSStringCreateWithUTF8CString(s)) - { - } - String(const JSStringRef& s) - : m_str(JSStringRetain(s)) - { - } - String(StringData str) - : String(str.data()) - { - } - String(const std::string& str) - : String(str.c_str()) - { - } - String(const StringType& o) - : String(o.m_str) - { - } - String(StringType&& o) - : m_str(o.m_str) - { - o.m_str = nullptr; - } - ~String() - { - if (m_str) { - JSStringRelease(m_str); - } - } - - operator JSStringRef() const - { - return m_str; - } - operator std::string() const - { - size_t max_size = JSStringGetMaximumUTF8CStringSize(m_str); - std::string string; - string.resize(max_size); - string.resize(JSStringGetUTF8CString(m_str, &string[0], max_size) - 1); - return string; - } -}; - -} // namespace js -} // namespace realm diff --git a/src/jsc/jsc_types.hpp b/src/jsc/jsc_types.hpp deleted file mode 100644 index 3aedaee329..0000000000 --- a/src/jsc/jsc_types.hpp +++ /dev/null @@ -1,64 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include -#include -#include - -#define HANDLESCOPE(context) - -#include "js_types.hpp" - - -namespace realm { -namespace jsc { - -struct Types { - using Context = JSContextRef; - using GlobalContext = JSGlobalContextRef; - using Value = JSValueRef; - using Object = JSObjectRef; - using String = JSStringRef; - using Function = JSObjectRef; - - using ConstructorCallback = JSObjectCallAsConstructorCallback; - using FunctionCallback = JSObjectCallAsFunctionCallback; - using PropertyGetterCallback = JSObjectGetPropertyCallback; - using PropertySetterCallback = JSObjectSetPropertyCallback; - using IndexPropertyGetterCallback = JSValueRef (*)(JSContextRef, JSObjectRef, uint32_t, JSValueRef*); - using IndexPropertySetterCallback = bool (*)(JSContextRef, JSObjectRef, uint32_t, JSValueRef, JSValueRef*); - using StringPropertyGetterCallback = JSObjectGetPropertyCallback; - using StringPropertySetterCallback = JSObjectSetPropertyCallback; - using StringPropertyEnumeratorCallback = JSObjectGetPropertyNamesCallback; -}; - -template -class ObjectWrap; - -using String = js::String; -using Context = js::Context; -using Value = js::Value; -using Function = js::Function; -using Object = js::Object; -using Exception = js::Exception; -using ReturnValue = js::ReturnValue; - -} // namespace jsc -} // namespace realm diff --git a/src/jsc/jsc_value.cpp b/src/jsc/jsc_value.cpp deleted file mode 100644 index 50623d190c..0000000000 --- a/src/jsc/jsc_value.cpp +++ /dev/null @@ -1,224 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include "jsc_value.hpp" - -#include "jsc_function.hpp" -#include "jsc_object.hpp" - -namespace realm { -namespace js { - -template <> -bool jsc::Value::is_binary(JSContextRef ctx, const JSValueRef& value) -{ - static jsc::String s_array_buffer = "ArrayBuffer"; - static jsc::String s_is_view = "isView"; - - JSObjectRef global_object = JSContextGetGlobalObject(ctx); - JSObjectRef array_buffer_constructor = jsc::Object::validated_get_constructor(ctx, global_object, s_array_buffer); - - // Value should either be an ArrayBuffer or ArrayBufferView (i.e. TypedArray or DataView). - if (JSValueIsInstanceOfConstructor(ctx, value, array_buffer_constructor, nullptr)) { - return true; - } - if (JSObjectRef object = JSValueToObject(ctx, value, nullptr)) { - // Check if value is an ArrayBufferView by calling ArrayBuffer.isView(val). - JSValueRef is_view = jsc::Object::call_method(ctx, array_buffer_constructor, s_is_view, 1, &object); - - return jsc::Value::to_boolean(ctx, is_view); - } - return false; -} - -template <> -JSValueRef jsc::Value::from_nonnull_binary(JSContextRef ctx, BinaryData data) -{ - static jsc::String s_buffer = "buffer"; - static jsc::String s_uint8_array = "Uint8Array"; - - size_t byte_count = data.size(); - JSValueRef byte_count_value = jsc::Value::from_number(ctx, byte_count); - JSObjectRef uint8_array_constructor = - jsc::Object::validated_get_constructor(ctx, JSContextGetGlobalObject(ctx), s_uint8_array); - JSObjectRef uint8_array = jsc::Function::construct(ctx, uint8_array_constructor, 1, &byte_count_value); - - for (uint32_t i = 0; i < byte_count; i++) { - JSValueRef num = jsc::Value::from_number(ctx, data[i]); - jsc::Object::set_property(ctx, uint8_array, i, num); - } - - return jsc::Object::validated_get_object(ctx, uint8_array, s_buffer); -} - -template <> -JSValueRef jsc::Value::from_decimal128(JSContextRef ctx, const Decimal128& value) -{ - static jsc::String s_realm = "Realm"; - static jsc::String s_decimal = "_Decimal128"; - static jsc::String s_from_string = "fromString"; - - if (value.is_null()) { - return JSValueMakeNull(ctx); - } - - JSObjectRef global_object = JSContextGetGlobalObject(ctx); - JSObjectRef realm_constructor = jsc::Object::validated_get_constructor(ctx, global_object, s_realm); - JSObjectRef decimal_constructor = jsc::Object::validated_get_constructor(ctx, realm_constructor, s_decimal); - - std::array args = {{jsc::Value::from_nonnull_string(ctx, jsc::String(value.to_string()))}}; - - return jsc::Object::call_method(ctx, decimal_constructor, s_from_string, args.size(), args.data()); -} - -template <> -JSValueRef jsc::Value::from_object_id(JSContextRef ctx, const ObjectId& value) -{ - static jsc::String s_realm = "Realm"; - static jsc::String s_object_id = "_ObjectId"; - - JSObjectRef global_object = JSContextGetGlobalObject(ctx); - JSObjectRef realm_constructor = jsc::Object::validated_get_constructor(ctx, global_object, s_realm); - JSObjectRef object_id_constructor = jsc::Object::validated_get_constructor(ctx, realm_constructor, s_object_id); - - std::array args{{jsc::Value::from_nonnull_string(ctx, jsc::String(value.to_string()))}}; - return jsc::Function::construct(ctx, object_id_constructor, args.size(), args.data()); -} - -template <> -JSValueRef jsc::Value::from_uuid(JSContextRef ctx, const UUID& value) -{ - static jsc::String s_realm = "Realm"; - static jsc::String s_uuid = "_UUID"; - - JSObjectRef global_object = JSContextGetGlobalObject(ctx); - JSObjectRef realm_constructor = jsc::Object::validated_get_constructor(ctx, global_object, s_realm); - JSObjectRef uuid_constructor = jsc::Object::validated_get_constructor(ctx, realm_constructor, s_uuid); - - std::array args{{jsc::Value::from_nonnull_string(ctx, jsc::String(value.to_string()))}}; - return jsc::Function::construct(ctx, uuid_constructor, args.size(), args.data()); -} - -template <> -OwnedBinaryData jsc::Value::to_binary(JSContextRef ctx, const JSValueRef& value) -{ - static jsc::String s_array_buffer = "ArrayBuffer"; - static jsc::String s_buffer = "buffer"; - static jsc::String s_byte_length = "byteLength"; - static jsc::String s_byte_offset = "byteOffset"; - static jsc::String s_is_view = "isView"; - static jsc::String s_uint8_array = "Uint8Array"; - - JSObjectRef global_object = JSContextGetGlobalObject(ctx); - JSObjectRef array_buffer_constructor = jsc::Object::validated_get_constructor(ctx, global_object, s_array_buffer); - JSObjectRef uint8_array_constructor = jsc::Object::validated_get_constructor(ctx, global_object, s_uint8_array); - JSValueRef uint8_array_arguments[3]; - uint32_t uint8_array_argc = 0; - - // Value should either be an ArrayBuffer or ArrayBufferView (i.e. TypedArray or DataView). - if (JSValueIsInstanceOfConstructor(ctx, value, array_buffer_constructor, nullptr)) { - uint8_array_arguments[0] = value; - uint8_array_argc = 1; - } - else if (JSObjectRef object = JSValueToObject(ctx, value, nullptr)) { - // Check if value is an ArrayBufferView by calling ArrayBuffer.isView(val). - JSValueRef is_view = jsc::Object::call_method(ctx, array_buffer_constructor, s_is_view, 1, &object); - - if (jsc::Value::to_boolean(ctx, is_view)) { - uint8_array_arguments[0] = jsc::Object::validated_get_object(ctx, object, s_buffer); - uint8_array_arguments[1] = jsc::Object::get_property(ctx, object, s_byte_offset); - uint8_array_arguments[2] = jsc::Object::get_property(ctx, object, s_byte_length); - uint8_array_argc = 3; - } - } - - if (!uint8_array_argc) { - throw std::runtime_error("Can only convert ArrayBuffer and TypedArray objects to binary"); - } - - JSObjectRef uint8_array = - jsc::Function::construct(ctx, uint8_array_constructor, uint8_array_argc, uint8_array_arguments); - uint32_t byte_count = jsc::Object::validated_get_length(ctx, uint8_array); - auto buffer = std::make_unique(byte_count); - - for (uint32_t i = 0; i < byte_count; i++) { - JSValueRef byteValue = jsc::Object::get_property(ctx, uint8_array, i); - buffer[i] = jsc::Value::to_number(ctx, byteValue); - } - - return OwnedBinaryData(std::move(buffer), byte_count); -} - -template <> -Decimal128 jsc::Value::to_decimal128(JSContextRef ctx, const JSValueRef& value) -{ - auto object = to_object(ctx, value); - // EJSON input supported (in RN only) for enabling debugging of synced realms. - auto ejson_property = jsc::Object::get_property(ctx, object, "$numberDecimal"); - - if (is_undefined(ctx, ejson_property)) { - static jsc::String s_to_string = "toString"; - JSValueRef args[] = {}; - JSValueRef as_string = jsc::Object::call_method(ctx, to_object(ctx, value), s_to_string, 0, args); - std::string str = to_string(ctx, as_string); - return Decimal128(StringData(str)); - } - else { - std::string str = to_string(ctx, ejson_property); - return Decimal128(StringData(str)); - } -} - -template <> -ObjectId jsc::Value::to_object_id(JSContextRef ctx, const JSValueRef& value) -{ - auto object = to_object(ctx, value); - // EJSON input supported (in RN only) for enabling debugging of synced realms. - auto ejson_property = jsc::Object::get_property(ctx, object, "$oid"); - - if (is_undefined(ctx, ejson_property)) { - static jsc::String s_to_hex_string = "toHexString"; - JSValueRef args[] = {}; - JSValueRef as_string = jsc::Object::call_method(ctx, to_object(ctx, value), s_to_hex_string, 0, args); - return ObjectId(std::string(to_string(ctx, as_string)).c_str()); - } - else { - return ObjectId(std::string(to_string(ctx, ejson_property)).c_str()); - } -} - -template <> -UUID jsc::Value::to_uuid(JSContextRef ctx, const JSValueRef& value) -{ - auto object = to_object(ctx, value); - // EJSON input supported (in RN only) for enabling debugging of synced realms. - auto ejson_property = jsc::Object::get_property(ctx, object, "$uuid"); - - if (is_undefined(ctx, ejson_property)) { - static jsc::String s_to_hex_string = "toHexString"; - JSValueRef args[] = {}; - JSValueRef as_string = jsc::Object::call_method(ctx, to_object(ctx, value), s_to_hex_string, 0, args); - return UUID(std::string(to_string(ctx, as_string)).c_str()); - } - else { - return UUID(std::string(to_string(ctx, ejson_property)).c_str()); - } -} - -} // namespace js -} // namespace realm diff --git a/src/jsc/jsc_value.hpp b/src/jsc/jsc_value.hpp deleted file mode 100644 index 369c05bf88..0000000000 --- a/src/jsc/jsc_value.hpp +++ /dev/null @@ -1,367 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include "jsc_protected.hpp" -#include "jsc_types.hpp" -#include "jsc_string.hpp" - -namespace realm { -namespace js { - -static inline bool is_object_of_type(JSContextRef ctx, JSValueRef value, jsc::String type) -{ - JSObjectRef global_object = JSContextGetGlobalObject(ctx); - JSValueRef exception = nullptr; - JSValueRef constructor = JSObjectGetProperty(ctx, global_object, type, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - bool result = JSValueIsInstanceOfConstructor(ctx, value, jsc::Value::validated_to_constructor(ctx, constructor), - &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - - return result; -} - -template <> -inline const char* jsc::Value::typeof(JSContextRef ctx, const JSValueRef& value) -{ - switch (JSValueGetType(ctx, value)) { - case kJSTypeNull: - return "null"; - case kJSTypeNumber: - return "number"; - case kJSTypeObject: - return "object"; - case kJSTypeString: - return "string"; - case kJSTypeBoolean: - return "boolean"; - case kJSTypeUndefined: - return "undefined"; -#if defined __IPHONE_12_2 || defined __MAC_10_14_4 - case kJSTypeSymbol: - return "symbol"; -#endif - } -} - -template <> -inline bool jsc::Value::is_array(JSContextRef ctx, const JSValueRef& value) -{ - // JSValueIsArray() is not available until iOS 9. - static const jsc::String type = "Array"; - return is_object_of_type(ctx, value, type); -} - -template <> -inline bool jsc::Value::is_array_buffer(JSContextRef ctx, const JSValueRef& value) -{ - static const jsc::String type = "ArrayBuffer"; - return is_object_of_type(ctx, value, type); -} - -template <> -inline bool jsc::Value::is_date(JSContextRef ctx, const JSValueRef& value) -{ - static const jsc::String type = "Date"; - return is_object_of_type(ctx, value, type); -} - -template <> -inline bool jsc::Value::is_boolean(JSContextRef ctx, const JSValueRef& value) -{ - return JSValueIsBoolean(ctx, value); -} - -template <> -inline bool jsc::Value::is_constructor(JSContextRef ctx, const JSValueRef& value) -{ - return JSValueIsObject(ctx, value) && JSObjectIsConstructor(ctx, (JSObjectRef)value); -} - -template <> -inline bool jsc::Value::is_error(JSContextRef ctx, const JSValueRef& value) -{ - static const jsc::String type = "Error"; - return is_object_of_type(ctx, value, type); -} - -template <> -inline bool jsc::Value::is_function(JSContextRef ctx, const JSValueRef& value) -{ - return JSValueIsObject(ctx, value) && JSObjectIsFunction(ctx, (JSObjectRef)value); -} - -template <> -inline bool jsc::Value::is_null(JSContextRef ctx, const JSValueRef& value) -{ - return JSValueIsNull(ctx, value); -} - -template <> -inline bool jsc::Value::is_number(JSContextRef ctx, const JSValueRef& value) -{ - return JSValueIsNumber(ctx, value); -} - -template <> -inline bool jsc::Value::is_object(JSContextRef ctx, const JSValueRef& value) -{ - return JSValueIsObject(ctx, value); -} - -template <> -inline bool jsc::Value::is_string(JSContextRef ctx, const JSValueRef& value) -{ - return JSValueIsString(ctx, value); -} - -template <> -bool jsc::Value::is_binary(JSContextRef ctx, const JSValueRef& value); - -template <> -inline bool jsc::Value::is_undefined(JSContextRef ctx, const JSValueRef& value) -{ - return JSValueIsUndefined(ctx, value); -} - -template <> -inline bool jsc::Value::is_valid(const JSValueRef& value) -{ - return value != nullptr; -} - -inline bool is_bson_type(JSContextRef ctx, const JSValueRef& value, std::string type) -{ - if (JSValueIsNull(ctx, value) || JSValueIsUndefined(ctx, value) || !JSValueIsObject(ctx, value)) { - return false; - } - - - JSValueRef error = nullptr; - JSObjectRef object = JSValueToObject(ctx, value, &error); - if (error) { - throw jsc::Exception(ctx, error); - } - - JSValueRef bson_type = JSObjectGetProperty(ctx, object, JSStringCreateWithUTF8CString("_bsontype"), &error); - if (error) { - throw jsc::Exception(ctx, error); - } - - if (JSValueIsUndefined(ctx, value)) { - return false; - } - - jsc::String bson_type_value = JSValueToStringCopy(ctx, bson_type, &error); - // Since the string's retain value is +2 here, we need to manually release it before returning. - JSStringRelease(bson_type_value); - if (error) { - throw jsc::Exception(ctx, error); - } - - return (std::string)bson_type_value == type; -} - -/** - * Checks if a `value` is an EJSON representation of a particular type (determined by the existance of a particular - * property). - */ -inline bool is_ejson_type(JSContextRef ctx, const JSValueRef& value, std::string property_name) -{ - if (JSValueIsNull(ctx, value) || JSValueIsUndefined(ctx, value) || !JSValueIsObject(ctx, value)) { - return false; - } - - JSValueRef error = nullptr; - JSObjectRef object = JSValueToObject(ctx, value, &error); - if (error) { - throw jsc::Exception(ctx, error); - } - - JSStringRef property_name_string = JSStringCreateWithUTF8CString(property_name.c_str()); - auto property = JSObjectGetProperty(ctx, object, property_name_string, &error); - if (error) { - throw jsc::Exception(ctx, error); - } - - return JSValueIsUndefined(ctx, property) == false; -} - -template <> -inline bool jsc::Value::is_decimal128(JSContextRef ctx, const JSValueRef& value) -{ - return is_bson_type(ctx, value, "Decimal128") || is_ejson_type(ctx, value, "$numberDecimal"); -} - -template <> -inline bool jsc::Value::is_object_id(JSContextRef ctx, const JSValueRef& value) -{ - return is_bson_type(ctx, value, "ObjectID") || is_ejson_type(ctx, value, "$oid"); -} - -template <> -inline bool jsc::Value::is_uuid(JSContextRef ctx, const JSValueRef& value) -{ - // TODO: is_ejson_type won't work for binary EJSON - return is_bson_type(ctx, value, "UUID") || is_ejson_type(ctx, value, "$uuid"); -} - -template <> -inline JSValueRef jsc::Value::from_boolean(JSContextRef ctx, bool boolean) -{ - return JSValueMakeBoolean(ctx, boolean); -} - -template <> -inline JSValueRef jsc::Value::from_null(JSContextRef ctx) -{ - return JSValueMakeNull(ctx); -} - -template <> -inline JSValueRef jsc::Value::from_number(JSContextRef ctx, double number) -{ - return JSValueMakeNumber(ctx, number); -} - -template <> -inline JSValueRef jsc::Value::from_nonnull_string(JSContextRef ctx, const jsc::String& string) -{ - return JSValueMakeString(ctx, string); -} - -template <> -inline JSValueRef jsc::Value::from_undefined(JSContextRef ctx) -{ - return JSValueMakeUndefined(ctx); -} - -template <> -JSValueRef jsc::Value::from_nonnull_binary(JSContextRef ctx, BinaryData data); - -template <> -JSValueRef jsc::Value::from_decimal128(JSContextRef ctx, const Decimal128& value); - -template <> -JSValueRef jsc::Value::from_object_id(JSContextRef ctx, const ObjectId& value); - -template <> -JSValueRef jsc::Value::from_uuid(JSContextRef ctx, const UUID& value); - -template <> -inline bool jsc::Value::to_boolean(JSContextRef ctx, const JSValueRef& value) -{ - return JSValueToBoolean(ctx, value); -} -template <> -inline jsc::String jsc::Value::to_string(JSContextRef ctx, const JSValueRef& value) -{ - JSValueRef exception = nullptr; - jsc::String string = JSValueToStringCopy(ctx, value, &exception); - - // Since the string's retain value is +2 here, we need to manually release it before returning. - JSStringRelease(string); - - if (exception) { - throw jsc::Exception(ctx, exception); - } - return string; -} - -template <> -inline double jsc::Value::to_number(JSContextRef ctx, const JSValueRef& value) -{ - JSValueRef exception = nullptr; - double number = JSValueToNumber(ctx, value, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - if (isnan(number)) { - throw std::invalid_argument( - util::format("Value '%1' not convertible to a number.", (std::string)to_string(ctx, value))); - } - return number; -} - - -template <> -inline JSObjectRef jsc::Value::to_object(JSContextRef ctx, const JSValueRef& value) -{ - JSValueRef exception = nullptr; - JSObjectRef object = JSValueToObject(ctx, value, &exception); - if (exception) { - throw jsc::Exception(ctx, exception); - } - return object; -} - -template <> -inline JSObjectRef jsc::Value::to_array(JSContextRef ctx, const JSValueRef& value) -{ - return to_object(ctx, value); -} - -template <> -inline JSObjectRef jsc::Value::to_constructor(JSContextRef ctx, const JSValueRef& value) -{ - return to_object(ctx, value); -} - -template <> -inline JSObjectRef jsc::Value::to_date(JSContextRef ctx, const JSValueRef& value) -{ - if (JSValueIsString(ctx, value)) { - JSValueRef error; - std::array args{value}; - if (JSObjectRef result = JSObjectMakeDate(ctx, args.size(), args.data(), &error)) { - return result; - } - else { - throw jsc::Exception(ctx, error); - } - } - return to_object(ctx, value); -} - -template <> -inline JSObjectRef jsc::Value::to_function(JSContextRef ctx, const JSValueRef& value) -{ - return to_object(ctx, value); -} - -template <> -OwnedBinaryData jsc::Value::to_binary(JSContextRef ctx, const JSValueRef& value); - -template <> -Decimal128 jsc::Value::to_decimal128(JSContextRef ctx, const JSValueRef& value); - -template <> -ObjectId jsc::Value::to_object_id(JSContextRef ctx, const JSValueRef& value); - -template <> -UUID jsc::Value::to_uuid(JSContextRef ctx, const JSValueRef& value); - -} // namespace js -} // namespace realm diff --git a/src/jsc/rpc.cpp b/src/jsc/rpc.cpp deleted file mode 100644 index 1b963a5d89..0000000000 --- a/src/jsc/rpc.cpp +++ /dev/null @@ -1,1235 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include -#include - -#include "rpc.hpp" -#include "jsc_init.hpp" - -#include -#include -#include -#include -#include "jsc_rpc_network_transport.hpp" -#include "js_app.hpp" - -#include "concurrent_deque.hpp" -#include -#include "jsc_types.hpp" -#include "jsc_protected.hpp" -#include "js_network_transport.hpp" - -using namespace realm; -using namespace realm::rpc; - -using json = nlohmann::json; - -using RPCObjectID = u_int64_t; -using RPCRequest = std::function; -using NetworkTransport = js::JavaScriptNetworkTransport; -using NetworkTransportFactory = typename NetworkTransport::NetworkTransportFactory; - -using Value = js::Value; -using Accessor = realm::js::NativeAccessor; -using AppClass = js::AppClass; - -namespace realm::rpc { - -class RPCWorker { -public: - RPCWorker(); - ~RPCWorker(); - - template - json add_task(Fn&&); - void invoke_callback(json); - json resolve_callback(json args); - std::future add_promise(); - - bool try_run_task(); - void stop(); - json try_pop_callback(); - bool should_stop(); - -private: - bool m_stop = false; - int m_depth = 0; -#if __APPLE__ - std::thread m_thread; - CFRunLoopRef m_loop; -#endif - ConcurrentDeque> m_tasks; - ConcurrentDeque> m_promises; - ConcurrentDeque m_callbacks; -}; - -class RPCServerImpl { -public: - RPCServerImpl(); - ~RPCServerImpl(); - json perform_request(std::string const& name, json&& args); - bool try_run_task(); - -private: - JSGlobalContextRef m_context; - std::mutex m_request_mutex; - std::map m_requests; - std::map> m_objects; - std::map> m_callbacks; - // The key here is the same as the value in m_callbacks. We use the raw pointer as a key here, - // because protecting the value in m_callbacks pins the function object and prevents it from being moved - // by the garbage collector upon compaction. - std::map m_callback_ids; - RPCObjectID m_session_id; - RPCWorker m_worker; - u_int64_t m_callback_call_counter; - uint64_t m_reset_counter = 0; - - std::mutex m_pending_callbacks_mutex; - std::map, std::promise> m_pending_callbacks; - - NetworkTransportFactory previous_transport_generator; - - static JSValueRef run_callback(JSContextRef, JSObjectRef, JSObjectRef, size_t, const JSValueRef[], - JSValueRef* exception); - - RPCObjectID store_object(JSObjectRef object); - JSObjectRef get_object(RPCObjectID) const; - JSObjectRef get_realm_constructor() const; - - json serialize_json_value(JSValueRef value); - JSValueRef deserialize_json_value(const json dict); -}; - -} // namespace realm::rpc - -namespace { -static const char* const RealmObjectTypesData = "data"; -static const char* const RealmObjectTypesDate = "date"; -static const char* const RealmObjectTypesDictionary = "dict"; -static const char* const RealmObjectTypesFunction = "function"; -static const char* const RealmObjectTypesList = "list"; -static const char* const RealmObjectTypesObject = "object"; -static const char* const RealmObjectTypesResults = "results"; -static const char* const RealmObjectTypesRealm = "realm"; -static const char* const RealmObjectTypesUser = "user"; -static const char* const RealmObjectTypesSession = "session"; -static const char* const RealmObjectTypesAsyncOpenTask = "asyncopentask"; -static const char* const RealmObjectTypesApp = "app"; -static const char* const RealmObjectTypesCredentials = "credentials"; -static const char* const RealmObjectTypesUndefined = "undefined"; -static const char* const RealmObjectTypesError = "error"; -static const char* const RealmObjectTypesFetchResponseHandler = "fetchresponsehandler"; -static const char* const RealmObjectTypesEmailPasswordAuth = "emailpasswordauth"; -static const char* const RealmObjectTypesEJSON = "ejson"; - -json serialize_object_schema(const realm::ObjectSchema& object_schema) -{ - std::vector properties; - - for (auto& prop : object_schema.persisted_properties) { - properties.push_back(prop.public_name.empty() ? prop.name : prop.public_name); - } - - for (auto& prop : object_schema.computed_properties) { - properties.push_back(prop.public_name.empty() ? prop.name : prop.public_name); - } - - return { - {"name", object_schema.name}, - {"properties", properties}, - }; -} - -template -json get_type(Container const& c) -{ - auto type = c.get_type(); - if (type == realm::PropertyType::Object) { - return serialize_object_schema(c.get_object_schema()); - } - return {{"type", js::local_string_for_property_type(type)}, {"optional", is_nullable(type)}}; -} - -RPCServerImpl*& get_rpc_server(JSGlobalContextRef ctx) -{ - static std::map s_map; - return s_map[ctx]; -} -} // namespace - -#ifdef __APPLE__ -void runLoopFunc(CFRunLoopRef loop, RPCWorker* rpcWorker) -{ - CFRunLoopPerformBlock(loop, kCFRunLoopDefaultMode, ^{ - rpcWorker->try_run_task(); - if (rpcWorker->should_stop()) { - CFRunLoopStop(CFRunLoopGetCurrent()); - } - else { - runLoopFunc(loop, rpcWorker); - } - }); - CFRunLoopWakeUp(loop); -} -#endif - -RPCWorker::RPCWorker() -{ -#ifdef __APPLE__ - m_thread = std::thread([this]() { - m_loop = CFRunLoopGetCurrent(); - runLoopFunc(m_loop, this); - CFRunLoopRun(); - }); -#endif -} - -RPCWorker::~RPCWorker() -{ - stop(); -} - -template -json RPCWorker::add_task(Fn&& fn) -{ - std::promise p; - auto future = p.get_future(); - m_promises.push_back(std::move(p)); - m_tasks.push_back([this, fn = std::move(fn)] { - auto result = fn(); - m_promises.pop_back().set_value(std::move(result)); - }); - return future.get(); -} - -void RPCWorker::invoke_callback(json callback) -{ - m_tasks.push_back([=, callback = std::move(callback)]() mutable { - if (m_depth == 1) { - // The callback was invoked directly from the event loop. Push it - // onto the queue of callbacks to be processed by /callbacks_poll - m_callbacks.push_back(std::move(callback)); - } - else if (auto promise = m_promises.try_pop_back(0)) { - // The callback was invoked from within a call to something else, - // and there's someone waiting for its result. - promise->set_value(std::move(callback)); - } - else { - // The callback was invoked from within a call to something else, - // but there's no one waiting for the result. Shouldn't be possible? - m_callbacks.push_back(std::move(callback)); - } - }); -} - -std::future RPCWorker::add_promise() -{ - std::promise p; - auto future = p.get_future(); - m_promises.push_back(std::move(p)); - return future; -} - -json RPCWorker::try_pop_callback() -{ - auto cb = m_callbacks.try_pop_back(0); - return cb ? *cb : json::object(); -} - -bool RPCWorker::try_run_task() -{ - if (m_stop) { - return true; - } - - // Use a 10 millisecond timeout to keep this thread unblocked. - if (auto task = m_tasks.try_pop_back(10)) { - ++m_depth; - (*task)(); - --m_depth; - return m_stop; - } - return false; -} - -bool RPCWorker::should_stop() -{ - return m_stop; -} - -void RPCWorker::stop() -{ - if (!m_stop) { - m_stop = true; -#if __APPLE__ - m_thread.join(); - m_loop = nullptr; -#endif - } -} - -static json read_object_properties(Object& object) -{ - json cache; - if (!object.is_valid()) { - return cache; - } - - // Send the values of the primitive and short string properties directly - // as the overhead of doing so is tiny compared to even a single RPC request - auto& object_schema = object.get_object_schema(); - auto obj = object.obj(); - for (auto& property : object_schema.persisted_properties) { - if (is_array(property.type)) { - continue; - } - if (is_nullable(property.type) && obj.is_null(property.column_key)) { - cache[property.name] = {{"value", json(nullptr)}}; - continue; - } - auto cache_value = [&](auto&& v) { - cache[property.name] = {{"value", v}}; - }; - switch (property.type & ~PropertyType::Flags) { - case PropertyType::Bool: - cache_value(obj.get(property.column_key)); - break; - case PropertyType::Int: - cache_value(obj.get(property.column_key)); - break; - case PropertyType::Float: - cache_value(obj.get(property.column_key)); - break; - case PropertyType::Double: - cache_value(obj.get(property.column_key)); - break; - case PropertyType::Date: { - auto ts = obj.get(property.column_key); - cache[property.name] = { - {"type", RealmObjectTypesDate}, - {"value", ts.get_seconds() * 1000.0 + ts.get_nanoseconds() / 1000000.0}, - }; - break; - } break; - case PropertyType::String: { - auto str = obj.get(property.column_key); - // A completely abitrary upper limit on how big of a string we'll pre-cache - if (str.size() < 100) { - cache_value(str); - } - break; - } - case PropertyType::Decimal: - cache[property.name] = { - {"type", RealmObjectTypesEJSON}, - {"value", {"$numberDecimal", obj.get(property.column_key).to_string()}}, - }; - break; - case PropertyType::ObjectId: - cache[property.name] = { - {"type", RealmObjectTypesEJSON}, - {"value", {"$oid", obj.get(property.column_key).to_string()}}, - }; - break; - case PropertyType::Data: - case PropertyType::Object: - break; - default: - REALM_UNREACHABLE(); - } - } - return cache; -} - -RPCServerImpl::RPCServerImpl() -{ - m_context = JSGlobalContextCreate(NULL); - get_rpc_server(m_context) = this; - m_callback_call_counter = 1; - - // Make the App use the RPC Network Transport from now on - previous_transport_generator = AppClass::transport_generator; - AppClass::transport_generator = - [](jsc::Types::Context ctx, - NetworkTransport::Dispatcher dispatcher) -> std::unique_ptr { - (void)dispatcher; // We don't need to use the dispatcher because JSC separately guarantees thread-safety. - return std::make_unique(ctx); - }; - - // JavaScriptCore crashes when trying to walk up the native stack to print the stacktrace. - // FIXME: Avoid having to do this! - static void (*setIncludesNativeCallStack)(JSGlobalContextRef, bool) = (void (*)(JSGlobalContextRef, bool))dlsym( - RTLD_DEFAULT, "JSGlobalContextSetIncludesNativeCallStackWhenReportingExceptions"); - if (setIncludesNativeCallStack) { - setIncludesNativeCallStack(m_context, false); - } - - m_requests["/create_session"] = [this](const json dict) { - RJSInitializeInContext(m_context, []() {}); - - jsc::String realm_string = "Realm"; - JSObjectRef realm_constructor = - jsc::Object::validated_get_constructor(m_context, JSContextGetGlobalObject(m_context), realm_string); - - // Enable the RCP network transport to issue calls to the remote fetch function - jsc::Types::Function fetch_function = - Value::validated_to_function(m_context, deserialize_json_value(dict["fetch"]), "fetch"); - RPCNetworkTransport::fetch_function = js::Protected(m_context, fetch_function); - - m_session_id = store_object(realm_constructor); - return (json){{"result", m_session_id}}; - }; - m_requests["/create_realm"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef realm_object = jsc::Function::construct(m_context, realm_constructor, arg_count, arg_values); - - JSObjectRef add_listener_method = - (JSObjectRef)jsc::Object::get_property(m_context, realm_object, "addListener"); - JSValueRef listener_args[] = {jsc::Value::from_string(m_context, "beforenotify"), - deserialize_json_value(dict["beforeNotify"])}; - jsc::Function::call(m_context, add_listener_method, realm_object, 2, listener_args); - - return (json){{"result", serialize_json_value(realm_object)}}; - }; - m_requests["/create_app"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef app_constructor = (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "App"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef app_object = jsc::Function::construct(m_context, app_constructor, arg_count, arg_values); - return (json){{"result", serialize_json_value(app_object)}}; - }; - m_requests["/create_user"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - - JSObjectRef sync_constructor = (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Sync"); - JSObjectRef user_constructor = (JSObjectRef)jsc::Object::get_property(m_context, sync_constructor, "User"); - JSObjectRef create_user_method = - (JSObjectRef)jsc::Object::get_property(m_context, user_constructor, "createUser"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef user_object = - (JSObjectRef)jsc::Function::call(m_context, create_user_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(user_object)}}; - }; - m_requests["/call_sync_function"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef sync_constructor = (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Sync"); - - std::string name = dict["name"]; - JSObjectRef method = (JSObjectRef)jsc::Object::get_property(m_context, sync_constructor, name); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - auto result = jsc::Function::call(m_context, method, arg_count, arg_values); - return (json){{"result", serialize_json_value(result)}}; - }; - m_requests["/_asyncOpen"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - - JSObjectRef _asyncOpen_method = - (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "_asyncOpen"); - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - auto result = jsc::Function::call(m_context, _asyncOpen_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(result)}}; - }; - m_requests["/call_method"] = [this](const json dict) { - JSObjectRef object = get_object(dict["id"].get()); - std::string method_string = dict["name"].get(); - JSObjectRef function = jsc::Object::validated_get_function(m_context, object, method_string); - - json args = dict["arguments"]; - size_t arg_count = args.size(); - JSValueRef arg_values[arg_count]; - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSValueRef result = jsc::Function::call(m_context, function, object, arg_count, arg_values); - return (json){{"result", serialize_json_value(result)}}; - }; - m_requests["/get_object"] = [this](const json dict) -> json { - RPCObjectID oid = dict["id"].get(); - json name = dict["name"]; - JSObjectRef object = get_object(oid); - if (!object) { - return {{"result", nullptr}}; - } - - json result; - if (jsc::Object::is_instance>(m_context, object)) { - auto obj = jsc::Object::get_internal>(m_context, object); - result = read_object_properties(*obj); - } - if (result.find(name) == result.end()) { - if (name.is_number()) { - auto key = name.get(); - result[key] = serialize_json_value(jsc::Object::get_property(m_context, object, key)); - } - else { - auto key = name.get(); - result[key] = serialize_json_value(jsc::Object::get_property(m_context, object, key)); - } - } - return {{"result", result}}; - }; - m_requests["/get_property"] = [this](const json dict) { - RPCObjectID oid = dict["id"].get(); - json name = dict["name"]; - - JSValueRef value; - if (JSObjectRef object = get_object(oid)) { - if (name.is_number()) { - value = jsc::Object::get_property(m_context, object, name.get()); - } - else { - value = jsc::Object::get_property(m_context, object, name.get()); - } - } - else { - value = jsc::Value::from_null(m_context); - } - - return (json){{"result", serialize_json_value(value)}}; - }; - m_requests["/set_property"] = [this](const json dict) { - RPCObjectID oid = dict["id"].get(); - json name = dict["name"]; - JSValueRef value = deserialize_json_value(dict["value"]); - - if (name.is_number()) { - jsc::Object::set_property(m_context, get_object(oid), name.get(), value); - } - else { - jsc::Object::set_property(m_context, get_object(oid), name.get(), value); - } - - return json::object(); - }; - m_requests["/dispose_object"] = [this](const json dict) { - RPCObjectID oid = dict["id"].get(); - m_objects.erase(oid); - return json::object(); - }; - m_requests["/clear_test_state"] = [this](const json dict) { - // The session ID points to the Realm constructor object, which should remain. - auto realm_constructor = m_objects[m_session_id]; - m_objects.clear(); - - if (realm_constructor) { - m_objects.emplace(m_session_id, realm_constructor); - } - - // The JS side of things only gives us the fetch function callback - // when creating a session so we need to hold onto it. - auto fetch_function = m_callbacks[0]; - - m_callbacks.clear(); - m_callback_ids.clear(); - m_callbacks[0] = fetch_function; - m_callback_ids[fetch_function] = 0; - ++m_reset_counter; - JSGarbageCollect(m_context); - js::clear_test_state(); - - return json::object(); - }; - m_requests["/set_versions"] = [this](const json dict) { - JSObjectRef versions = - jsc::Value::validated_to_object(m_context, deserialize_json_value(dict["versions"]), "versions"); - AppClass::package_version = jsc::Object::validated_get_string(m_context, versions, "packageVersion"); - AppClass::platform_context = jsc::Object::validated_get_string(m_context, versions, "platformContext"); - AppClass::platform_os = jsc::Object::validated_get_string(m_context, versions, "platformOs"); - AppClass::platform_version = jsc::Object::validated_get_string(m_context, versions, "platformVersion"); - return json::object(); - }; - m_requests["/_anonymous"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef credentials_constructor = - (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Credentials"); - JSObjectRef anonymous_method = - (JSObjectRef)jsc::Object::get_property(m_context, credentials_constructor, "anonymous"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); // should be zero - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef credentials_object = - (JSObjectRef)jsc::Function::call(m_context, anonymous_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(credentials_object)}}; - }; - m_requests["/_facebook"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef credentials_constructor = - (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Credentials"); - JSObjectRef facebook_method = - (JSObjectRef)jsc::Object::get_property(m_context, credentials_constructor, "facebook"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); // should be one - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef credentials_object = - (JSObjectRef)jsc::Function::call(m_context, facebook_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(credentials_object)}}; - }; - m_requests["/_apple"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef credentials_constructor = - (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Credentials"); - JSObjectRef apple_method = - (JSObjectRef)jsc::Object::get_property(m_context, credentials_constructor, "apple"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); // should be one - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef credentials_object = - (JSObjectRef)jsc::Function::call(m_context, apple_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(credentials_object)}}; - }; - m_requests["/_emailPassword"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef credentials_constructor = - (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Credentials"); - JSObjectRef email_password_method = - (JSObjectRef)jsc::Object::get_property(m_context, credentials_constructor, "emailPassword"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); // should be two - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef credentials_object = - (JSObjectRef)jsc::Function::call(m_context, email_password_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(credentials_object)}}; - }; - m_requests["/_function"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef credentials_constructor = - (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Credentials"); - JSObjectRef function_method = - (JSObjectRef)jsc::Object::get_property(m_context, credentials_constructor, "function"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); // should be one - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef credentials_object = - (JSObjectRef)jsc::Function::call(m_context, function_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(credentials_object)}}; - }; - m_requests["/_google"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef credentials_constructor = - (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Credentials"); - JSObjectRef google_method = - (JSObjectRef)jsc::Object::get_property(m_context, credentials_constructor, "google"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); // should be one - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef credentials_object = - (JSObjectRef)jsc::Function::call(m_context, google_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(credentials_object)}}; - }; - m_requests["/_userApiKey"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef credentials_constructor = - (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Credentials"); - JSObjectRef user_api_key_method = - (JSObjectRef)jsc::Object::get_property(m_context, credentials_constructor, "userApiKey"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); // should be one - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef credentials_object = - (JSObjectRef)jsc::Function::call(m_context, user_api_key_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(credentials_object)}}; - }; - m_requests["/_serverApiKey"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef credentials_constructor = - (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Credentials"); - JSObjectRef server_api_key_method = - (JSObjectRef)jsc::Object::get_property(m_context, credentials_constructor, "serverApiKey"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); // should be one - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef credentials_object = - (JSObjectRef)jsc::Function::call(m_context, server_api_key_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(credentials_object)}}; - }; - m_requests["/_jwt"] = [this](const json dict) { - JSObjectRef realm_constructor = get_realm_constructor(); - JSObjectRef credentials_constructor = - (JSObjectRef)jsc::Object::get_property(m_context, realm_constructor, "Credentials"); - JSObjectRef jwt_method = (JSObjectRef)jsc::Object::get_property(m_context, credentials_constructor, "jwt"); - - json::array_t args = dict["arguments"]; - size_t arg_count = args.size(); // should be one - JSValueRef arg_values[arg_count]; - - for (size_t i = 0; i < arg_count; i++) { - arg_values[i] = deserialize_json_value(args[i]); - } - - JSObjectRef credentials_object = - (JSObjectRef)jsc::Function::call(m_context, jwt_method, arg_count, arg_values); - return (json){{"result", serialize_json_value(credentials_object)}}; - }; -} - -RPCServerImpl::~RPCServerImpl() -{ - m_worker.stop(); - - // The protected values should be unprotected before releasing the context. - m_objects.clear(); - m_callbacks.clear(); - - // Restore the previous transport generator - AppClass::transport_generator = previous_transport_generator; - - get_rpc_server(m_context) = nullptr; - JSGlobalContextRelease(m_context); -} - -/** - * Asks the client to execute a callback and awaits the result. - */ -JSValueRef RPCServerImpl::run_callback(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object, size_t argc, - const JSValueRef arguments[], JSValueRef* exception) -{ - RPCServerImpl* server = get_rpc_server(JSContextGetGlobalContext(ctx)); - if (!server) { - return JSValueMakeUndefined(ctx); - } - - u_int64_t counter = server->m_callback_call_counter++; - // The first argument was curried to be the callback id. - auto it = server->m_callback_ids.find(function); - if (it == server->m_callback_ids.end()) { - // Callback will no longer exist if it was pending while clearTestState() - // was called. Just return undefined when that happens. - return JSValueMakeUndefined(ctx); - } - RPCObjectID callback_id = it->second; - JSObjectRef arguments_array = jsc::Object::create_array(ctx, uint32_t(argc), arguments); - json arguments_json = server->serialize_json_value(arguments_array); - json this_json = server->serialize_json_value(this_object); - - std::future future; - { - std::lock_guard lock(server->m_pending_callbacks_mutex); - future = server->m_pending_callbacks[{callback_id, counter}].get_future(); - } - - // The next task on the stack will instruct the JS to run this callback. - // This captures references since it will be executed before exiting this function. - server->m_worker.invoke_callback({{"callback", callback_id}, - {"this", this_json}, - {"arguments", arguments_json}, - {"callback_call_counter", counter}}); - - uint64_t reset_counter = server->m_reset_counter; - while (!server->try_run_task() && future.wait_for(std::chrono::microseconds(100)) != std::future_status::ready && - reset_counter == server->m_reset_counter) - ; - - if (reset_counter != server->m_reset_counter) { - // clearTestState() was called while the callback was pending - return JSValueMakeUndefined(ctx); - } - - json results = future.get(); - // The callback id should be identical! - assert(callback_id == results["callback"].get()); - - json error = results["error"]; - if (!error.is_null()) { - JSStringRef message = JSStringCreateWithUTF8CString(error.get().c_str()); - JSValueRef arguments[]{JSValueMakeString(ctx, message)}; - JSStringRelease(message); - JSObjectRef error = JSObjectMakeError(ctx, 1, arguments, nullptr); - *exception = error; - - json stack = results["stack"]; - if (stack.is_string()) { - JSStringRef stack_json = JSStringCreateWithUTF8CString(stack.get().c_str()); - JSValueRef array = JSValueMakeFromJSONString(ctx, stack_json); - JSStringRelease(stack_json); - JSStringRef key = JSStringCreateWithUTF8CString("stack"); - JSObjectSetProperty(ctx, error, key, array, 0, nullptr); - JSStringRelease(key); - } - return nullptr; - } - - return server->deserialize_json_value(results["result"]); -} - -json RPCServerImpl::perform_request(std::string const& name, json&& args) -{ - std::lock_guard lock(m_request_mutex); - - // Only create_session is allowed without the correct session id (since it creates the session id). - if (name != "/create_session" && m_session_id != args["sessionId"].get()) { - return {{"error", "Invalid session ID"}}; - } - - auto resolve_callback = [&] { - auto callback_id = args["callback"].get(); - auto callback_counter = args["callback_call_counter"].get(); - std::lock_guard lock(m_pending_callbacks_mutex); - auto cb = m_pending_callbacks.find({callback_id, callback_counter}); - if (cb != m_pending_callbacks.end()) { - cb->second.set_value(args); - m_pending_callbacks.erase(cb); - } - }; - - // The callback_result message contains the return value (or exception) of a callback ran by run_callback(). - if (name == "/callback_result") { - std::future result = m_worker.add_promise(); - resolve_callback(); - return result.get(); - } - if (name == "/callback_poll_result") { - resolve_callback(); - return m_worker.try_pop_callback(); - } - if (name == "/callbacks_poll") { - return m_worker.try_pop_callback(); - } - - RPCRequest* action = &m_requests[name]; - REALM_ASSERT_RELEASE(action && *action); - - return m_worker.add_task([=] { - try { - return (*action)(args); - } - catch (jsc::Exception const& ex) { - json exceptionAsJson = nullptr; - try { - exceptionAsJson = serialize_json_value(ex); - } - catch (...) { - exceptionAsJson = { - {"error", - "An exception occured while processing the request. Could not serialize the exception as JSON"}}; - } - return (json){ - {"error", exceptionAsJson}, - {"message", ex.what()}, - }; - } - catch (std::exception const& exception) { - return (json){{"error", exception.what()}}; - } - }); -} - -bool RPCServerImpl::try_run_task() -{ - return m_worker.try_run_task(); -} - -RPCObjectID RPCServerImpl::store_object(JSObjectRef object) -{ - static RPCObjectID s_next_id = 1; - - RPCObjectID next_id = s_next_id++; - m_objects.emplace(next_id, js::Protected(m_context, object)); - return next_id; -} - -JSObjectRef RPCServerImpl::get_object(RPCObjectID oid) const -{ - auto it = m_objects.find(oid); - return it == m_objects.end() ? nullptr : static_cast(it->second); -} - -JSObjectRef RPCServerImpl::get_realm_constructor() const -{ - JSObjectRef realm_constructor = m_session_id ? JSObjectRef(get_object(m_session_id)) : NULL; - if (!realm_constructor) { - throw std::runtime_error("Realm constructor not found!"); - } - return realm_constructor; -} - -json RPCServerImpl::serialize_json_value(JSValueRef js_value) -{ - switch (JSValueGetType(m_context, js_value)) { - case kJSTypeUndefined: - return json::object(); - case kJSTypeNull: - return {{"value", json(nullptr)}}; - case kJSTypeBoolean: - return {{"value", jsc::Value::to_boolean(m_context, js_value)}}; - case kJSTypeNumber: - return {{"value", jsc::Value::to_number(m_context, js_value)}}; - case kJSTypeString: - return {{"value", jsc::Value::to_string(m_context, js_value)}}; - case kJSTypeObject: - break; -#if defined __IPHONE_12_2 || defined __MAC_10_14_4 - case kJSTypeSymbol: - break; -#endif - } - - JSObjectRef js_object = jsc::Value::validated_to_object(m_context, js_value); - - if (jsc::Object::is_instance>(m_context, js_object)) { - auto object = jsc::Object::get_internal>(m_context, js_object); - return {{"type", RealmObjectTypesObject}, - {"id", store_object(js_object)}, - {"schema", serialize_object_schema(object->get_object_schema())}, - {"cache", read_object_properties(*object)}}; - } - else if (jsc::Object::is_instance>(m_context, js_object)) { - auto list = jsc::Object::get_internal>(m_context, js_object); - return { - {"type", RealmObjectTypesList}, - {"id", store_object(js_object)}, - {"dataType", js::local_string_for_property_type(list->get_type() & ~realm::PropertyType::Flags)}, - {"optional", is_nullable(list->get_type())}, - }; - } - else if (jsc::Object::is_instance>(m_context, js_object)) { - auto results = jsc::Object::get_internal>(m_context, js_object); - return { - {"type", RealmObjectTypesResults}, - {"id", store_object(js_object)}, - {"dataType", js::local_string_for_property_type(results->get_type() & ~realm::PropertyType::Flags)}, - {"optional", is_nullable(results->get_type())}, - }; - } - else if (jsc::Object::is_instance>(m_context, js_object)) { - auto realm = jsc::Object::get_internal>(m_context, js_object); - json realm_dict{ - {"_isPartialRealm", - serialize_json_value(jsc::Object::get_property(m_context, js_object, "_isPartialRealm"))}, - {"inMemory", serialize_json_value(jsc::Object::get_property(m_context, js_object, "inMemory"))}, - {"path", serialize_json_value(jsc::Object::get_property(m_context, js_object, "path"))}, - {"readOnly", serialize_json_value(jsc::Object::get_property(m_context, js_object, "readOnly"))}, - {"syncSession", serialize_json_value(jsc::Object::get_property(m_context, js_object, "syncSession"))}, - }; - return {{"type", RealmObjectTypesRealm}, - {"id", store_object(js_object)}, - {"realmId", (uintptr_t)realm->get()}, - {"data", realm_dict}}; - } -#if REALM_ENABLE_SYNC - else if (jsc::Object::is_instance>(m_context, js_object)) { - return { - {"type", RealmObjectTypesUser}, - {"id", store_object(js_object)}, - }; - } - else if (jsc::Object::is_instance>(m_context, js_object)) { - json session_dict{ - {"user", serialize_json_value(jsc::Object::get_property(m_context, js_object, "user"))}, - {"config", serialize_json_value(jsc::Object::get_property(m_context, js_object, "config"))}, - }; - return {{"type", RealmObjectTypesSession}, {"id", store_object(js_object)}, {"data", session_dict}}; - } - else if (jsc::Object::is_instance>(m_context, js_object)) { - return { - {"type", RealmObjectTypesAsyncOpenTask}, - {"id", store_object(js_object)}, - }; - } - else if (jsc::Object::is_instance>(m_context, js_object)) { - return { - {"type", RealmObjectTypesApp}, - {"id", store_object(js_object)}, - }; - } - else if (jsc::Object::is_instance>(m_context, js_object)) { - return { - {"type", RealmObjectTypesCredentials}, - {"id", store_object(js_object)}, - }; - } - else if (jsc::Object::is_instance>(m_context, js_object)) { - return { - {"type", RealmObjectTypesFetchResponseHandler}, - {"id", store_object(js_object)}, - }; - } - else if (jsc::Object::is_instance>(m_context, js_object)) { - return { - {"type", RealmObjectTypesEmailPasswordAuth}, - {"id", store_object(js_object)}, - }; - } -#endif - else if (jsc::Value::is_array(m_context, js_object)) { - uint32_t length = jsc::Object::validated_get_length(m_context, js_object); - std::vector array; - for (uint32_t i = 0; i < length; i++) { - array.push_back(serialize_json_value(jsc::Object::get_property(m_context, js_object, i))); - } - return {{"value", array}}; - } - else if (jsc::Value::is_binary(m_context, js_object)) { - auto data = jsc::Value::to_binary(m_context, js_object); - - std::string encoded; - encoded.reserve(realm::util::base64_encoded_size(data.size())); - encoded.resize(realm::util::base64_encode(data.data(), data.size(), encoded.data(), encoded.capacity())); - - return { - {"type", RealmObjectTypesData}, - {"value", encoded}, - }; - } - else if (jsc::Value::is_date(m_context, js_object)) { - return { - {"type", RealmObjectTypesDate}, - {"value", jsc::Value::to_number(m_context, js_object)}, - }; - } - else if (jsc::Value::is_error(m_context, js_object)) { - return { - {"type", RealmObjectTypesError}, - {"message", serialize_json_value(jsc::Object::get_property(m_context, js_object, "message"))}, - {"stack", serialize_json_value(jsc::Object::get_property(m_context, js_object, "stack"))}, - }; - } - else if (jsc::Value::is_function(m_context, js_object)) { - auto it = m_callback_ids.find(js_object); - if (it != m_callback_ids.end()) { - return {{"type", RealmObjectTypesFunction}, {"value", it->second}}; - } - else { - return {{"type", RealmObjectTypesFunction}, {"value", it->second}}; - } - return json::object(); - } - else { - // Serialize this JS object as a plain object since it doesn't match any known types above. - std::vector keys; - std::vector values; - - // Use the enumarable properties - std::vector js_keys = jsc::Object::get_property_names(m_context, js_object); - for (auto& js_key : js_keys) { - JSValueRef js_value = jsc::Object::get_property(m_context, js_object, js_key); - keys.push_back(js_key); - values.push_back(serialize_json_value(js_value)); - } - - return { - {"type", RealmObjectTypesDictionary}, - {"keys", keys}, - {"values", values}, - }; - } - assert(0); -} - -JSValueRef RPCServerImpl::deserialize_json_value(const json dict) -{ - json oid = dict.value("id", json()); - if (oid.is_number()) { - return m_objects[oid.get()]; - } - - json value = dict.value("value", json()); - json type = dict.value("type", json()); - - if (type.is_string()) { - std::string type_string = type.get(); - - if (type_string == RealmObjectTypesFunction) { - RPCObjectID callback_id = value.get(); - - if (!m_callbacks.count(callback_id)) { - JSObjectRef callback = JSObjectMakeFunctionWithCallback(m_context, nullptr, run_callback); - m_callbacks.emplace(callback_id, js::Protected(m_context, callback)); - m_callback_ids.emplace(callback, callback_id); - } - - return m_callbacks.at(callback_id); - } - else if (type_string == RealmObjectTypesDictionary) { - JSObjectRef js_object = jsc::Object::create_empty(m_context); - json keys = dict["keys"]; - json values = dict["values"]; - size_t count = keys.size(); - - for (size_t i = 0; i < count; i++) { - std::string js_key = keys.at(i); - JSValueRef js_value = deserialize_json_value(values.at(i)); - jsc::Object::set_property(m_context, js_object, js_key, js_value); - } - - return js_object; - } - else if (type_string == RealmObjectTypesData) { - auto bytes = realm::util::base64_decode_to_vector(value.get()); - if (!bytes) { - throw std::runtime_error("Failed to decode base64 encoded data"); - } - return jsc::Value::from_binary(m_context, realm::BinaryData(bytes->data(), bytes->size())); - } - else if (type_string == RealmObjectTypesDate) { - return jsc::Object::create_date(m_context, value.get()); - } - else if (type_string == RealmObjectTypesUndefined) { - return jsc::Value::from_undefined(m_context); - } - else if (type_string == RealmObjectTypesEJSON) { - JSObjectRef js_object = jsc::Object::create_empty(m_context); - for (auto& el : value.items()) { - auto el_value = jsc::Value::from_string(m_context, el.value().get()); - jsc::Object::set_property(m_context, js_object, el.key(), el_value); - } - return js_object; - } - assert(0); - } - - if (value.is_null()) { - return jsc::Value::from_null(m_context); - } - else if (value.is_boolean()) { - return jsc::Value::from_boolean(m_context, value.get()); - } - else if (value.is_number()) { - return jsc::Value::from_number(m_context, value.get()); - } - else if (value.is_string()) { - return jsc::Value::from_string(m_context, value.get()); - } - else if (value.is_array()) { - size_t count = value.size(); - JSValueRef js_values[count]; - - for (size_t i = 0; i < count; i++) { - js_values[i] = deserialize_json_value(value.at(i)); - } - - return jsc::Object::create_array(m_context, (uint32_t)count, js_values); - } - else { - throw std::runtime_error("deserialize_json_value: Unkown value"); - } - - assert(0); -} - -RPCServer::RPCServer() - : m_impl(new RPCServerImpl()) -{ -} - -RPCServer::~RPCServer() = default; - -std::string RPCServer::perform_request(std::string const& name, std::string const& json_args) -{ - return m_impl->perform_request(name, json::parse(json_args)).dump(); -} - -bool RPCServer::try_run_task() -{ - return m_impl->try_run_task(); -} diff --git a/src/jsc/rpc.hpp b/src/jsc/rpc.hpp deleted file mode 100644 index 0923b9be72..0000000000 --- a/src/jsc/rpc.hpp +++ /dev/null @@ -1,40 +0,0 @@ -//////////////////////////////////////////////////////////////////////////// -// -// Copyright 2016 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. -// -//////////////////////////////////////////////////////////////////////////// - -#pragma once - -#include -#include - -namespace realm { -namespace rpc { - -class RPCServerImpl; -class RPCServer { -public: - RPCServer(); - ~RPCServer(); - std::string perform_request(std::string const& name, std::string const& json_args); - bool try_run_task(); - -private: - std::unique_ptr m_impl; -}; - -} // namespace rpc -} // namespace realm diff --git a/src/jsi/CMakeLists.txt b/src/jsi/CMakeLists.txt new file mode 100644 index 0000000000..af9809edda --- /dev/null +++ b/src/jsi/CMakeLists.txt @@ -0,0 +1,19 @@ +# Using Node.js to resolve the path to the "react-native" package. +# This enables building for iOS on the end-users machine, +# where "react-native" is installed as a sibling to our package instead of being a dev-dependency of our package. + +execute_process( + COMMAND node --print "path.dirname(require.resolve('react-native/package.json'))" + OUTPUT_VARIABLE REACT_NATIVE_ROOT_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE +) +set(JSI_HEADER_DIR "${REACT_NATIVE_ROOT_DIR}/ReactCommon/jsi") +message(STATUS "Getting JSI headers from ${JSI_HEADER_DIR}") + +add_library(realm-js-jsi OBJECT + jsi_init.cpp +) + +target_include_directories(realm-js-jsi PRIVATE ${JSI_HEADER_DIR}) + +target_link_libraries(realm-js-jsi PUBLIC realm-js-shared) diff --git a/src/jsi/jsi_class.hpp b/src/jsi/jsi_class.hpp new file mode 100644 index 0000000000..ccd5f22787 --- /dev/null +++ b/src/jsi/jsi_class.hpp @@ -0,0 +1,746 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2021 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. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once +#include "jsi_types.hpp" +#include "jsi_return_value.hpp" +#include "jsi_string.hpp" +#include "jsi_object.hpp" + +#include "js_class.hpp" +#include "js_util.hpp" + +#include +#include +#include +#include +#include +#include +#include + +namespace realm::js { + +template +struct RealmObjectClass; +template +class RealmClass; + +namespace fbjsi = facebook::jsi; + +template <> +struct Arguments { + const std::vector valStorage; + const JsiEnv ctx; + const size_t count; + const JsiVal* const value; + + Arguments(JsiEnv env, size_t argc, const JsiVal* argv) + : ctx(env) + , count(argc) + , value(argv) + { + } + + Arguments(JsiEnv env, size_t argc, const fbjsi::Value* argv) + : valStorage([&] { + std::vector out; + out.reserve(argc); + for (size_t i = 0; i < argc; i++) { + out.emplace_back(env, argv[i]); + } + return out; + }()) + , ctx(env) + , count(argc) + , value(valStorage.data()) + { + } + + // If moving or copying were allowed, we would need to update value's pointer + Arguments(Arguments&&) = delete; + + JsiVal operator[](size_t index) const noexcept + { + if (index >= count) { + return ctx.undefined(); + } + return ctx(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)); + } + } + + void validate_count(size_t expected) const + { + if (count != expected) { + throw std::invalid_argument( + util::format("Invalid arguments: %1 expected, but %2 supplied.", expected, count)); + } + } + + void validate_between(size_t min, size_t max) const + { + if (count < min || count > max) { + throw std::invalid_argument( + util::format("Invalid arguments: expected between %1 and %2, but %3 supplied.", min, max, count)); + } + } +}; + +namespace realmjsi { // realm::js::realmjsi + +inline std::optional ObjectGetOwnPropertyDescriptor(JsiEnv env, const fbjsi::Object& target, + const std::string& name) +{ + auto obj = js::globalType(env, "Object"); + auto res = obj.getPropertyAsFunction(env, "getOwnPropertyDescriptor").callWithThis(env, obj, target, name); + if (!res.isObject()) + return {}; + return std::move(res).getObject(env); +} + +inline void ObjectSetPrototypeOf(JsiEnv env, const fbjsi::Value& target, const fbjsi::Value& proto) +{ + auto obj = js::globalType(env, "Object"); + obj.getPropertyAsFunction(env, "setPrototypeOf").callWithThis(env, obj, target, proto); +} + +inline JsiObj ObjectCreate(JsiEnv env, const fbjsi::Object& proto) +{ + auto obj = js::globalType(env, "Object"); + return env(obj.getPropertyAsFunction(env, "create").callWithThis(env, obj, proto)).asObject(); +} + +inline void defineProperty(JsiEnv env, const fbjsi::Object& target, StringData name, const fbjsi::Object& descriptor) +{ + auto objClass = js::globalType(env, "Object"); + objClass.getPropertyAsFunction(env, "defineProperty") + .callWithThis(env, objClass, target, str(env, name), descriptor); +}; + +inline void copyProperty(JsiEnv env, const fbjsi::Object& from, const fbjsi::Object& to, const std::string& name) +{ + auto prop = ObjectGetOwnPropertyDescriptor(env, from, name); + REALM_ASSERT_RELEASE(prop); + defineProperty(env, to, name, *prop); +} + +inline constexpr const char g_internal_field[] = "__Realm_internal"; + +template +using ClassDefinition = js::ClassDefinition; + +using ConstructorType = js::ConstructorType; +using ArgumentsMethodType = js::ArgumentsMethodType; +using ReturnValue = js::ReturnValue; +using Arguments = js::Arguments; +using PropertyType = js::PropertyType; +using IndexPropertyType = js::IndexPropertyType; +using StringPropertyType = js::StringPropertyType; + +template +class Wrapper : public fbjsi::HostObject { +public: + template >> + Wrapper(Args&&... args) + : obj(std::forward(args)...) + { + } + + T obj; +}; + +template +inline T& unwrap(Wrapper& wrapper) +{ + return wrapper.obj; +} + +template +inline T& unwrap(const std::shared_ptr>& wrapper) +{ + return unwrap(*wrapper); +} + +template +inline T& unwrap(JsiEnv env, const fbjsi::Object& wrapper) +{ + return unwrap(wrapper.getHostObject>(env)); +} + +template +inline T& unwrap(JsiEnv env, const fbjsi::Value& wrapper) +{ + return unwrap(env, wrapper.asObject(env)); +} + +template +inline T& unwrap(const JsiObj& wrapper) +{ + return unwrap(wrapper.env(), wrapper.get()); +} + +template +inline T& unwrap(const JsiVal& wrapper) +{ + return unwrap(wrapper.env(), wrapper.get()); +} + +template +inline T* unwrapUnique(JsiEnv env, const U& arg) +{ + return unwrap>(env, arg).get(); +} + +template +JsiObj wrap(JsiEnv env, T arg) +{ + return env(fbjsi::Object::createFromHostObject(env, std::make_shared>(std::move(arg)))); +} + +template >> +JsiObj wrap(JsiEnv env, Args&&... args) +{ + return env(fbjsi::Object::createFromHostObject(env, std::make_shared>(std::forward(args)...))); +} + +template +JsiObj wrapUnique(JsiEnv env, T* arg) +{ + return wrap(env, std::unique_ptr(arg)); +} + +template +class ObjectWrap { +public: + using Internal = typename T::Internal; + using ParentClassType = typename T::Parent; + + // NOTE: if this is static, it won't support multiple runtimes. + // Also, may need to suppress destruction. + inline static std::optional s_ctor; + + /** + * @brief callback for invalid access to index setters + * Throws an error when a users attemps to write to an index on a type that + * doesn't support it. + * + * @return nothing; always throws + */ + static fbjsi::Value readonly_index_setter_callback(fbjsi::Runtime& env, const fbjsi::Value& thisVal, + const fbjsi::Value* args, size_t count) + { + throw fbjsi::JSError(env, "Cannot assign to index"); + } + + /** + * @brief callback for invalid access to property setters + * Trows an error when a user attempts to write to a read-only property + * + * @param propname name of the property the user is trying to write to + * @return nothin; always throws + */ + static fbjsi::Value readonly_setter_callback(fbjsi::Runtime& env, const fbjsi::Value& thisVal, + const fbjsi::Value* args, size_t count, std::string const& propname) + { + throw fbjsi::JSError(env, util::format("Cannot assign to read only property '%1'", propname)); + } + + static JsiFunc create_constructor(JsiEnv env) + { + if (s_ctor) { + return *s_ctor; + } + + auto& s_type = get_class(); + + auto nativeFunc = !bool(s_type.constructor) + ? fbjsi::Value() + : fbjsi::Function::createFromHostFunction( + env, propName(env, s_type.name), /* paramCount verified by callback */ 0, + [](fbjsi::Runtime& rt, const fbjsi::Value&, const fbjsi::Value* args, + size_t count) -> fbjsi::Value { + REALM_ASSERT_RELEASE(count >= 1); + auto env = JsiEnv(rt); + auto& s_type = get_class(); + auto arguments = Arguments{env, count - 1, args + 1}; + s_type.constructor(env, env(args[0]).asObject(), arguments); + return fbjsi::Value(); + }); + + s_ctor = env(globalType(env, "Function") + .call(env, "nativeFunc", + util::format(R"( + return function %1(...args) { + // Allow explicit construction only for classes with a constructor + if (new.target && !nativeFunc) { + throw TypeError("Illegal constructor"); + } + if (nativeFunc) + nativeFunc(this, ...args); + + if ('_proxyWrapper' in %1) + return %1._proxyWrapper(this); + })", + s_type.name)) + .asObject(env) + .asFunction(env) + .call(env, std::move(nativeFunc)) + .asObject(env) + .asFunction(env)); + + js::Context::register_invalidator([] { + // Ensure the static constructor is destructed when the runtime goes away. + // This is to avoid the reassignment of s_ctor throwing because the runtime has disappeared. + s_ctor.reset(); + }); + + for (auto&& [name, prop] : s_type.static_properties) { + auto desc = fbjsi::Object(env); + if (prop.getter) { + desc.setProperty(env, "get", funcVal(env, "get_" + name, 0, prop.getter)); + } + if (prop.setter) { + desc.setProperty(env, "set", funcVal(env, "set_" + name, 1, prop.setter)); + } + else { + desc.setProperty( + env, "set", + funcVal(env, "set_" + name, 0, + std::bind(ObjectWrap::readonly_setter_callback, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, name))); + } + defineProperty(env, *s_ctor, name, desc); + } + + for (auto&& [name, method] : s_type.static_methods) { + auto desc = fbjsi::Object(env); + desc.setProperty(env, "value", + funcVal(env, name, /* paramCount must be verified by callback */ 0, method)); + defineProperty(env, *s_ctor, name, desc); + } + + auto proto = (*s_ctor)->getPropertyAsObject(env, "prototype"); + + for (auto&& [name, prop] : s_type.properties) { + auto desc = fbjsi::Object(env); + if (prop.getter) { + desc.setProperty(env, "get", funcVal(env, "get_" + name, 0, prop.getter)); + } + if (prop.setter) { + desc.setProperty(env, "set", funcVal(env, "set_" + name, 1, prop.setter)); + } + else { + desc.setProperty( + env, "set", + funcVal(env, "set_" + name, 0, + std::bind(ObjectWrap::readonly_setter_callback, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, name))); + } + defineProperty(env, proto, name, desc); + } + + for (auto&& [name, method] : s_type.methods) { + auto desc = fbjsi::Object(env); + desc.setProperty(env, "value", + funcVal(env, name, /* paramCount must be verified by callback */ 0, method)); + defineProperty(env, proto, name, desc); + } + + if constexpr (!std::is_void_v) { + REALM_ASSERT_RELEASE(ObjectWrap::s_ctor); + JsiFunc parentCtor = *ObjectWrap::s_ctor; + + auto parentProto = parentCtor->getProperty(env, "prototype"); + if (parentProto.isUndefined()) { + throw std::runtime_error("undefined 'prototype' on parent constructor"); + } + + ObjectSetPrototypeOf(env, fbjsi::Value(env, proto), fbjsi::Value(std::move(parentProto))); + ObjectSetPrototypeOf(env, fbjsi::Value(env, s_ctor->get()), fbjsi::Value(std::move(parentCtor.get()))); + } + + if (s_type.index_accessor) { + // Code below assumes getter is present, and it doesn't make sense to have setter without one. + REALM_ASSERT_RELEASE(s_type.index_accessor.getter); + + // XXX Do we want to trap things like ownKeys() and getOwnPropertyDescriptors() to support for...in? + auto [getter, setter] = s_type.index_accessor; + auto desc = fbjsi::Object(env); + desc.setProperty( + env, "value", + globalType(env, "Function") + .call(env, "getter", "setter", R"( + const integerPattern = /^-?\d+$/; + function getIndex(prop) { + if (typeof prop === "string" && integerPattern.test(prop)) { + return parseInt(prop, 10); + } else { + return Number.NaN; + } + } + const handler = { + ownKeys(target) { + const out = Reflect.ownKeys(target) + const end = target.length + for (let i = 0; i < end; i++) { + out.push(String(i)); + } + return out; + }, + getOwnPropertyDescriptor(target, prop) { + const index = getIndex(prop); + if (Number.isNaN(index)) { + return Reflect.getOwnPropertyDescriptor(...arguments); + } else if (index >= 0 && index < target.length) { + return { + configurable: true, + enumerable: true, + }; + } + }, + get(target, prop, receiver) { + const index = getIndex(prop); + if (Number.isNaN(index)) { + return Reflect.get(...arguments); + } else if (index >= 0 && index < target.length) { + return getter(target, index); + } + }, + set(target, prop, value, receiver) { + const index = getIndex(prop); + if (Number.isNaN(index)) { + return Reflect.set(...arguments); + } else if (index < 0) { + // This mimics realm::js::validated_positive_index + throw new Error(`Index ${index} cannot be less than zero.`); + } else { + return setter(target, index, value); + } + } + } + return (obj) => new Proxy(obj, handler); + )") + .asObject(env) + .asFunction(env) + .call(env, funcVal(env, "getter", 0, getter), + funcVal(env, "setter", 1, setter ? setter : ObjectWrap::readonly_index_setter_callback)) + .asObject(env) + .asFunction(env)); + defineProperty(env, *s_ctor, "_proxyWrapper", desc); + } + + return *s_ctor; + } + + static JsiObj create_instance(JsiEnv env, Internal* ptr = nullptr) + { + auto proto = (*s_ctor)->getPropertyAsObject(env, "prototype"); + auto obj = ObjectCreate(env, proto); + set_internal(env, obj, ptr); + + auto wrapper = (*s_ctor)->getProperty(env, "_proxyWrapper"); + if (!wrapper.isUndefined()) { + obj = env(wrapper.asObject(env).asFunction(env).call(env, std::move(obj.get()))).asObject(); + } + + return obj; + } + + static JsiObj create_instance_by_schema(JsiEnv env, JsiFunc& constructor, const realm::ObjectSchema& schema, + Internal* internal = nullptr) + { + return create_instance_by_schema(env, &constructor, schema, internal); + } + static JsiObj create_instance_by_schema(JsiEnv env, const realm::ObjectSchema& schema, + Internal* internal = nullptr) + { + return create_instance_by_schema(env, nullptr, schema, internal); + } + + static void on_context_destroy(JsiEnv, std::string realmPath) + { + get_schemaObjectTypes().erase(realmPath); + } + + static bool is_instance(JsiEnv env, JsiObj object) + { + return object->instanceOf(env, *s_ctor); + } + + static Internal* get_internal(JsiEnv env, const JsiObj& object) + { + auto internal = object->getProperty(env, g_internal_field); + if (internal.isUndefined()) { + // In the case of a user opening a Realm with a class-based model, + // the user defined constructor will get called before the "internal" property has been set. + if constexpr (std::is_same_v>) + return nullptr; + throw fbjsi::JSError(env, "no internal field"); + } + // The following check is disabled to support user defined classes that doesn't extend Realm.Object + // if (!JsiObj(object)->instanceOf(env, *s_ctor)) { + // throw fbjsi::JSError(env, "calling method on wrong type of object"); + // } + return unwrapUnique(env, std::move(internal)); + } + static void set_internal(JsiEnv env, const JsiObj& object, Internal* data) + { + auto desc = fbjsi::Object(env); + desc.setProperty(env, "value", wrapUnique(env, data)); + desc.setProperty(env, "configurable", true); + defineProperty(env, object, g_internal_field, desc); + } + +private: + static fbjsi::Value funcVal(JsiEnv env, const std::string& name, size_t args, fbjsi::HostFunctionType&& func) + { + if (!func) + return fbjsi::Value(); + return fbjsi::Value( + fbjsi::Function::createFromHostFunction(env, propName(env, name), uint32_t(args), std::move(func))); + }; + + static void defineSchemaProperties(JsiEnv env, const fbjsi::Object& constructorPrototype, + const realm::ObjectSchema& schema, bool redefine) + { + // Do the same thing for all computed and persisted properties + auto loopBody = [&](const Property& property) { + const auto& name = property.public_name.empty() ? property.name : property.public_name; + // TODO should this use hasOwnProperty? + if (!redefine && constructorPrototype.hasProperty(env, str(env, name))) { + return; + } + + auto desc = fbjsi::Object(env); + desc.setProperty(env, "enumerable", true); + + desc.setProperty(env, "get", + funcVal(env, "get_" + name, 0, + [name = String(name)](fbjsi::Runtime& rt, const fbjsi::Value& thisVal, + const fbjsi::Value* args, size_t count) { + if (count != 0) + throw fbjsi::JSError(rt, "getters take no arguments"); + return get_class().string_accessor.getter(rt, thisVal, name); + })); + desc.setProperty(env, "set", + funcVal(env, "set_" + name, 1, + [name = String(name)](fbjsi::Runtime& rt, const fbjsi::Value& thisVal, + const fbjsi::Value* args, size_t count) { + if (count != 1) + throw fbjsi::JSError(rt, "setters take exactly 1 argument"); + return get_class().string_accessor.setter(rt, thisVal, name, args[0]); + })); + + defineProperty(env, constructorPrototype, name, desc); + }; + + for (auto&& property : schema.persisted_properties) { + loopBody(property); + } + for (auto&& property : schema.computed_properties) { + loopBody(property); + } + } + + static JsiObj create_instance_by_schema(JsiEnv env, JsiFunc* maybeConstructor, const realm::ObjectSchema& schema, + Internal* internal = nullptr) + { + auto& s_schemaObjectTypes = get_schemaObjectTypes(); + auto& s_class = get_class(); + + bool isRealmObjectClass = std::is_same_v>; + if (!isRealmObjectClass) { + throw fbjsi::JSError(env, "Creating instances by schema is supported for RealmObjectClass only"); + } + + if (!internal) { + throw fbjsi::JSError( + env, "RealmObjectClass requires an internal realm object when creating instances by schema"); + } + + REALM_ASSERT_RELEASE(!s_class.index_accessor); // assume we don't need a ProxyWrapper + + auto config = internal->realm()->config(); + std::string path = config.path; + auto version = internal->realm()->schema_version(); + std::string schemaName = schema.name + ":" + std::to_string(version); + + const JsiFunc& realmObjectClassConstructor = *ObjectWrap::s_ctor; + + auto& schemaObjects = s_schemaObjectTypes[path]; + + // jsi::Symbol externalSymbol = ExternalSymbol; + + // if we are creating a RealmObject from schema with no user defined constructor + if (!maybeConstructor) { + // 1.Check by name if the constructor is already created for this RealmObject + if (!schemaObjects.count(schemaName)) { + // 2.Create the constructor + // create an anonymous RealmObject function + auto schemaObjectConstructor = globalType(env, "Function") + .callAsConstructor(env, "return function () {}") + .asObject(env) + .asFunction(env) + .call(env) + .asObject(env) + .asFunction(env); + + + auto schemaProto = schemaObjectConstructor.getProperty(env, "prototype"); + ObjectSetPrototypeOf(env, schemaProto, realmObjectClassConstructor->getProperty(env, "prototype")); + ObjectSetPrototypeOf(env, JsiVal(env(schemaObjectConstructor)), JsiVal(realmObjectClassConstructor)); + + defineSchemaProperties(env, std::move(schemaProto).asObject(env), schema, true); + + schemaObjects.emplace(schemaName, std::move(schemaObjectConstructor)); + } + } + else { + // creating a RealmObject with user defined constructor + auto& constructor = *maybeConstructor; + + bool schemaExists = schemaObjects.count(schemaName); + if (schemaExists) { + // check if constructors have changed for the same schema object and name + if (!fbjsi::Function::strictEquals(env, schemaObjects.at(schemaName), constructor)) { + schemaExists = false; + schemaObjects.erase(schemaName); + } + } + + if (!schemaExists) { + schemaObjects.emplace(schemaName, JsiFunc(constructor).get()); + auto constructorPrototype = constructor->getPropertyAsObject(env, "prototype"); + + // get all properties from the schema + defineSchemaProperties(env, env(constructorPrototype), schema, false); + } + } + + const auto& schemaObjectCtor = schemaObjects.at(schemaName); + auto constructorPrototype = schemaObjectCtor.getPropertyAsObject(env, "prototype"); + auto instance = ObjectCreate(env, constructorPrototype); + set_internal(env, instance, internal); + return instance; + } + + static auto& get_class() + { + // TODO this is silly. These should be static properties. + static T s_class; + return s_class; + } + + inline static auto& get_schemaObjectTypes() + { + // NOTE: this being static prevents using multiple runtimes. + static std::unordered_map> s_schemaObjectTypes; + return s_schemaObjectTypes; + } +}; + +} // namespace realmjsi + +template +class ObjectWrap : public realm::js::realmjsi::ObjectWrap {}; + +template +fbjsi::Value wrap(fbjsi::Runtime& rt, const fbjsi::Value& thisVal, const fbjsi::Value* args, size_t count) +{ + auto env = JsiEnv(rt); + auto result = realmjsi::ReturnValue(env); + auto arguments = realmjsi::Arguments{env, count, args}; + + F(env, env(thisVal).asObject(), arguments, result); + return std::move(result).ToValue(); +} + +template +fbjsi::Value wrap(fbjsi::Runtime& rt, const fbjsi::Value& thisVal, const fbjsi::Value* args, size_t count) +{ + auto env = JsiEnv(rt); + auto result = realmjsi::ReturnValue(env); + auto arguments = realmjsi::Arguments{env, count, args}; + arguments.validate_count(0); + + F(env, env(thisVal).asObject(), result); + return std::move(result).ToValue(); +} + +template +fbjsi::Value wrap(fbjsi::Runtime& rt, const fbjsi::Value& thisVal, const fbjsi::Value* args, size_t count) +{ + auto env = JsiEnv(rt); + auto arguments = realmjsi::Arguments{env, count, args}; + arguments.validate_count(1); + + F(env, env(thisVal).asObject(), JsiVal(env, args[0])); + + return fbjsi::Value(); +} + +template +fbjsi::Value wrap(fbjsi::Runtime& rt, const fbjsi::Value&, const fbjsi::Value* args, size_t count) +{ + REALM_ASSERT_RELEASE(count == 2); + auto env = JsiEnv(rt); + auto out = realmjsi::ReturnValue(env); + F(env, env(args[0]).asObject(), uint32_t(args[1].asNumber()), out); + return std::move(out).ToValue(); +} + +template +fbjsi::Value wrap(fbjsi::Runtime& rt, const fbjsi::Value&, const fbjsi::Value* args, size_t count) +{ + REALM_ASSERT_RELEASE(count == 3); + auto env = JsiEnv(rt); + return fbjsi::Value(F(env, env(args[0]).asObject(), uint32_t(args[1].asNumber()), env(args[2]))); +} + +template +fbjsi::Value wrap(fbjsi::Runtime& rt, const fbjsi::Value& thisVal, const realmjsi::String& str) +{ + auto env = JsiEnv(rt); + auto result = realmjsi::ReturnValue(env); + F(env, env(thisVal).asObject(), str, result); + return std::move(result).ToValue(); +} + +template +fbjsi::Value wrap(fbjsi::Runtime& rt, const fbjsi::Value& thisVal, const realmjsi::String& str, + const fbjsi::Value& value) +{ + auto env = JsiEnv(rt); + F(env, env(thisVal).asObject(), str, env(value)); + return fbjsi::Value(); +} + +template +fbjsi::Value wrap(fbjsi::Runtime& rt, const fbjsi::Value& thisVal, const fbjsi::Value* args, size_t count) +{ + // This is only used in the JSC impl. + REALM_UNREACHABLE(); +} + +} // namespace realm::js diff --git a/src/jsc/jsc_externs.hpp b/src/jsi/jsi_externs.hpp similarity index 85% rename from src/jsc/jsc_externs.hpp rename to src/jsi/jsi_externs.hpp index 7152b57296..5bb5a4534e 100644 --- a/src/jsc/jsc_externs.hpp +++ b/src/jsi/jsi_externs.hpp @@ -1,3 +1,7 @@ +#pragma once + +#include + namespace realm { namespace js { diff --git a/src/jsi/jsi_function.hpp b/src/jsi/jsi_function.hpp new file mode 100644 index 0000000000..e1ecd4093c --- /dev/null +++ b/src/jsi/jsi_function.hpp @@ -0,0 +1,67 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2021 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. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "jsi_types.hpp" +#include "jsi_externs.hpp" + +namespace realm { +namespace js { + +template +inline T flush_and_return(T&& val) +{ + flush_ui_queue(); + return val; +} + +template <> +inline JsiVal realmjsi::Function::call(JsiEnv env, const JsiFunc& function, size_t argc, const JsiVal arguments[]) +{ + return flush_and_return(env(function->call(env, env.args(arguments, argc), argc))); +} + +template <> +inline JsiVal realmjsi::Function::call(JsiEnv env, const JsiFunc& function, const JsiObj& this_object, size_t argc, + const JsiVal arguments[]) +{ + return flush_and_return(env(function->callWithThis(env, this_object, env.args(arguments, argc), argc))); +} + +template <> +inline JsiVal realmjsi::Function::callback(JsiEnv env, const JsiFunc& function, size_t argc, const JsiVal arguments[]) +{ + return flush_and_return(env(function->call(env, env.args(arguments, argc), argc))); +} +template <> +inline JsiVal realmjsi::Function::callback(JsiEnv env, const JsiFunc& function, const JsiObj& this_object, + size_t argc, const JsiVal arguments[]) +{ + return flush_and_return(env(function->callWithThis(env, this_object, env.args(arguments, argc), argc))); +} + +template <> +inline JsiObj realmjsi::Function::construct(JsiEnv env, const JsiFunc& function, size_t argc, + const JsiVal arguments[]) +{ + return flush_and_return(env(function->callAsConstructor(env, env.args(arguments, argc), argc).asObject(env))); +} + +} // namespace js +} // namespace realm diff --git a/src/jsc/jsc_init.cpp b/src/jsi/jsi_init.cpp similarity index 50% rename from src/jsc/jsc_init.cpp rename to src/jsi/jsi_init.cpp index cd883fdc39..73eef2ba55 100644 --- a/src/jsc/jsc_init.cpp +++ b/src/jsi/jsi_init.cpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2016 Realm Inc. +// Copyright 2021 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16,77 +16,66 @@ // //////////////////////////////////////////////////////////////////////////// -#include -#include -#include -#include -#include -#include -#include +#include "jsi_init.hpp" -#include "jsc_init.hpp" -#include "platform.hpp" +#if !REALM_ENABLE_SYNC +#pragma comment(lib, "ws2_32.lib") +#pragma comment(lib, "crypt32") +#endif +#include "js_realm.hpp" #include "js_notifications.hpp" -namespace realm { -namespace jsc { -js::Protected ObjectDefineProperty; -js::Protected FunctionPrototype; -js::Protected RealmObjectClassConstructor; -js::Protected RealmObjectClassConstructorPrototype; -} // namespace jsc +#include +#include +namespace realmjsi = realm::js::realmjsi; +namespace fbjsi = facebook::jsi; + +namespace realm { namespace js { + std::function flush_ui_queue; + } // namespace js } // namespace realm -extern "C" { - -using namespace realm; -using namespace realm::jsc; - -JSObjectRef RJSConstructorCreate(JSContextRef ctx) -{ - return js::RealmClass::create_constructor(ctx); -} - -void RJSInitializeInContext(JSContextRef ctx, std::function flush_ui_queue) +namespace realm::js::jsi { +extern "C" void realm_jsi_init(fbjsi::Runtime& rt, fbjsi::Object& exports, std::function flush_ui_queue) { - static const jsc::String realm_string = "Realm"; - - JSObjectRef global_object = JSContextGetGlobalObject(ctx); - - jsc_class_init(ctx, global_object, flush_ui_queue); - - JSObjectRef realm_constructor = RJSConstructorCreate(ctx); + // Store the function used to flush React Native microtask queue + js::flush_ui_queue = flush_ui_queue; - jsc::Object::set_property(ctx, global_object, realm_string, realm_constructor, - js::ReadOnly | js::DontEnum | js::DontDelete); + auto env = JsiEnv(rt); + fbjsi::Function realm_constructor = js::RealmClass::create_constructor(env); + auto name = realm_constructor.getProperty(env, "name").asString(env); + exports.setProperty(env, std::move(name), std::move(realm_constructor)); } -void RJSInvalidateCaches() +extern "C" void realm_jsi_invalidate_caches() { // Close all cached Realms realm::_impl::RealmCoordinator::clear_all_caches(); // Clear the Object Store App cache, to prevent instances from using a context that was released realm::app::App::clear_cached_apps(); // Clear notifications - realm::js::notifications::NotificationBucket::clear(); - realm::js::notifications::NotificationBucket::Token>::clear(); - realm::js::notifications::NotificationBucket::Token>::clear(); + realm::js::notifications::NotificationBucket::clear(); + realm::js::notifications::NotificationBucket::Token>::clear(); + realm::js::notifications::NotificationBucket::Token>::clear(); + // Ensure all registered invalidators get notified that the runtime is going away. + realm::js::Context::invalidate(); } // Note: This must be called before RJSInvalidateCaches, otherwise the app cache // will have been cleared and so no sync sessions will be closed -void RJSCloseSyncSessions() +extern "C" void realm_jsi_close_sync_sessions() { // Force all sync sessions to close immediately. This prevents the new JS thread // from opening a new sync session while the old one is still active when reloading // in dev mode. realm::app::App::close_all_sync_sessions(); } +} // namespace realm::js::jsi -} // extern "C" +// TODO hook up as TurboModule diff --git a/src/jsc/jsc_init.h b/src/jsi/jsi_init.h similarity index 74% rename from src/jsc/jsc_init.h rename to src/jsi/jsi_init.h index e607241072..65dd7dcad3 100644 --- a/src/jsc/jsc_init.h +++ b/src/jsi/jsi_init.h @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2016 Realm Inc. +// Copyright 2021 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,17 +18,17 @@ #pragma once -#include -#include +#import +#import #ifdef __cplusplus extern "C" { #endif -JSObjectRef RJSConstructorCreate(JSContextRef ctx); -void RJSInitializeInContext(JSContextRef ctx, std::function flush_ui_queue); -void RJSInvalidateCaches(); -void RJSCloseSyncSessions(); +namespace jsi = facebook::jsi; +void realm_jsi_init(jsi::Runtime& rt, jsi::Object& exports, std::function flush_ui_queue); +void realm_jsi_invalidate_caches(); +void realm_jsi_close_sync_sessions(); #ifdef __cplusplus } diff --git a/src/jsc/jsc_init.hpp b/src/jsi/jsi_init.hpp similarity index 72% rename from src/jsc/jsc_init.hpp rename to src/jsi/jsi_init.hpp index 671120fa02..28e787359d 100644 --- a/src/jsc/jsc_init.hpp +++ b/src/jsi/jsi_init.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2016 Realm Inc. +// Copyright 2021 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,18 +18,13 @@ #pragma once -#include "jsc_init.h" -#include "jsc_string.hpp" -#include "jsc_protected.hpp" -#include "jsc_context.hpp" -#include "jsc_value.hpp" -#include "jsc_object.hpp" -#include "jsc_function.hpp" -#include "jsc_exception.hpp" -#include "jsc_return_value.hpp" -#include "jsc_class.hpp" +#include "jsi_string.hpp" +#include "jsi_protected.hpp" +#include "jsi_function.hpp" +#include "jsi_value.hpp" +#include "jsi_object.hpp" +#include "jsi_return_value.hpp" +#include "jsi_class.hpp" // FIXME: js_object_accessor.hpp includes js_list.hpp which includes js_object_accessor.hpp. #include "js_object_accessor.hpp" - -#include "js_realm.hpp" diff --git a/src/jsi/jsi_object.hpp b/src/jsi/jsi_object.hpp new file mode 100644 index 0000000000..13476fc380 --- /dev/null +++ b/src/jsi/jsi_object.hpp @@ -0,0 +1,227 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2021 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. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "jsi_types.hpp" +#include "jsi_string.hpp" + +namespace realm { +namespace js { + +namespace realmjsi { +// forward-declare JSI ObjectWrap from jsi_class.hpp +template +class ObjectWrap; +} // namespace realmjsi + +namespace fbjsi = facebook::jsi; + +#if 0 +inline napi_property_attributes operator|(napi_property_attributes a, PropertyAttributes b) { + int flag = napi_default; + + if ((b & DontEnum) != DontEnum) { + flag |= napi_enumerable; + } + + if ((b & DontDelete) != DontDelete) { + flag |= napi_configurable; + } + + if ((b & ReadOnly) != ReadOnly) { + flag |= napi_writable; + } + + napi_property_attributes napi_flag = static_cast(a | flag); + return napi_flag; +} +#endif + +template <> +inline JsiVal realmjsi::Object::get_property(JsiEnv env, const JsiObj& object, StringData key) +{ + return env(object->getProperty(env, propName(env, key))); +} + +template <> +inline JsiVal realmjsi::Object::get_property(JsiEnv env, const JsiObj& object, const realmjsi::String& key) +{ + return env(object->getProperty(env, propName(env, key))); +} + +template <> +inline JsiVal realmjsi::Object::get_property(JsiEnv env, const JsiObj& object, uint32_t index) +{ + if (object->isArray(env)) + return env(object->asArray(env).getValueAtIndex(env, index)); + return realmjsi::Object::get_property(env, object, std::to_string(index)); +} + +template <> +inline void realmjsi::Object::set_property(JsiEnv env, JsiObj& object, const realmjsi::String& key, + const JsiVal& value, PropertyAttributes attributes) +{ + if (attributes) { + auto desc = fbjsi::Object(env); + desc.setProperty(env, "configurable", !(attributes & DontDelete)); + desc.setProperty(env, "enumerable", !(attributes & DontEnum)); + desc.setProperty(env, "writable", !(attributes & ReadOnly)); + desc.setProperty(env, "value", value); + + auto objClass = env->global().getPropertyAsObject(env, "Object"); + objClass.getPropertyAsFunction(env, "defineProperty") + .callWithThis(env, objClass, object, str(env, key), std::move(desc)); + } + else { + object->setProperty(env, propName(env, key), value); + } +} + +template <> +inline void realmjsi::Object::set_property(JsiEnv env, JsiObj& object, uint32_t index, const JsiVal& value) +{ + if (object->isArray(env)) + return object->asArray(env).setValueAtIndex(env, index, value); + return realmjsi::Object::set_property(env, object, std::to_string(index), value); +} + +template <> +inline std::vector realmjsi::Object::get_property_names(JsiEnv env, const JsiObj& object) +{ + auto namesArray = object->getPropertyNames(env); + + size_t count = namesArray.length(env); + std::vector names; + names.reserve(count); + + for (size_t i = 0; i < count; i++) { + names.push_back(namesArray.getValueAtIndex(env, i).asString(env).utf8(env)); + } + + return names; +} + +template <> +inline JsiVal realmjsi::Object::get_prototype(JsiEnv env, const JsiObj& object) +{ + auto objClass = env->global().getPropertyAsObject(env, "Object"); + return env(objClass.getPropertyAsFunction(env, "getPrototypeOf").callWithThis(env, objClass, object)); +} + +template <> +inline void realmjsi::Object::set_prototype(JsiEnv env, const JsiObj& object, const JsiVal& prototype) +{ + auto objClass = env->global().getPropertyAsObject(env, "Object"); + objClass.getPropertyAsFunction(env, "setPrototypeOf").callWithThis(env, objClass, object, prototype); +} + +template <> +inline JsiObj realmjsi::Object::create_empty(JsiEnv env) +{ + return JsiObj(env); +} + +template <> +inline JsiObj realmjsi::Object::create_array(JsiEnv env, uint32_t length, const JsiVal values[]) +{ + fbjsi::Array array = fbjsi::Array(env, length); + for (uint32_t i = 0; i < length; i++) { + array.setValueAtIndex(env, i, values[i]); + } + return env(std::move(array)); +} + +template <> +inline JsiObj realmjsi::Object::create_date(JsiEnv env, double time) +{ + return env(env->global().getPropertyAsFunction(env, "Date").callAsConstructor(env, time).asObject(env)); +} + +template <> +template +inline JsiObj realmjsi::Object::create_instance(JsiEnv env, typename ClassType::Internal* internal) +{ + return realmjsi::ObjectWrap::create_instance(env, internal); +} + +template <> +template +inline JsiObj realmjsi::Object::create_instance_by_schema(JsiEnv env, JsiFunc& constructor, + const realm::ObjectSchema& schema, + typename ClassType::Internal* internal) +{ + return realmjsi::ObjectWrap::create_instance_by_schema(env, constructor, schema, internal); +} + +template <> +template +inline JsiObj realmjsi::Object::create_instance_by_schema(JsiEnv env, const realm::ObjectSchema& schema, + typename ClassType::Internal* internal) +{ + return realmjsi::ObjectWrap::create_instance_by_schema(env, schema, internal); +} + +template +inline void on_context_destroy(JsiEnv env, std::string realmPath) +{ + realmjsi::ObjectWrap::on_context_destroy(env, realmPath); +} + +template <> +template +inline bool realmjsi::Object::is_instance(JsiEnv env, const JsiObj& object) +{ + return realmjsi::ObjectWrap::is_instance(env, object); +} + +template <> +template +inline typename ClassType::Internal* realmjsi::Object::get_internal(JsiEnv env, const JsiObj& object) +{ + return realmjsi::ObjectWrap::get_internal(env, object); +} + +template <> +template +inline void realmjsi::Object::set_internal(JsiEnv env, JsiObj& object, typename ClassType::Internal* internal) +{ + return realmjsi::ObjectWrap::set_internal(env, object, internal); +} + +template <> +inline void realmjsi::Object::set_global(JsiEnv env, const realmjsi::String& key, const JsiVal& value) +{ + auto global = env.global(); + Object::set_property(env, global, key, value); +} + +template <> +inline JsiVal realmjsi::Object::get_global(JsiEnv env, const realmjsi::String& key) +{ + return Object::get_property(env, env.global(), key); +} + +template <> +inline JsiVal realmjsi::Exception::value(JsiEnv env, const std::string& message) +{ + return str(env, message); +} + +} // namespace js +} // namespace realm diff --git a/src/jsi/jsi_protected.hpp b/src/jsi/jsi_protected.hpp new file mode 100644 index 0000000000..255e35ab3f --- /dev/null +++ b/src/jsi/jsi_protected.hpp @@ -0,0 +1,87 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2016 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. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "jsi_types.hpp" + +namespace realm { +namespace js { + +template <> +class Protected : public JsiVal { +public: + Protected(JsiEnv, JsiVal value) + : JsiVal(std::move(value)) + { + } + struct Comparator { + bool operator()(const Protected& a, const Protected& b) const + { + return a == b; + } + }; +}; + +template <> +class Protected : public JsiObj { +public: + Protected(JsiEnv, JsiObj value) + : JsiObj(std::move(value)) + { + } + struct Comparator { + bool operator()(const Protected& a, const Protected& b) const + { + return a == b; + } + }; +}; + +template <> +class Protected : public JsiFunc { +public: + Protected(JsiEnv, JsiFunc value) + : JsiFunc(std::move(value)) + { + } + struct Comparator { + bool operator()(const Protected& a, const Protected& b) const + { + return a == b; + } + }; +}; + +template <> +class Protected : public JsiEnv { +public: + Protected(JsiEnv env) + : JsiEnv(env) + { + } + struct Comparator { + bool operator()(const Protected& a, const Protected& b) const + { + return a == b; + } + }; +}; + +} // namespace js +} // namespace realm diff --git a/src/jsc/jsc_return_value.hpp b/src/jsi/jsi_return_value.hpp similarity index 52% rename from src/jsc/jsc_return_value.hpp rename to src/jsi/jsi_return_value.hpp index 7bffadc8b5..ae99fe8009 100644 --- a/src/jsc/jsc_return_value.hpp +++ b/src/jsi/jsi_return_value.hpp @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////// // -// Copyright 2016 Realm Inc. +// Copyright 2021 Realm Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -20,78 +20,110 @@ #include -#include "jsc_types.hpp" -#include "jsc_string.hpp" +#include "jsi_types.hpp" +#include "jsi_string.hpp" namespace realm { namespace js { +namespace fbjsi = facebook::jsi; + template <> -class ReturnValue { - const JSContextRef m_context; - JSValueRef m_value = nullptr; +class ReturnValue { + JsiEnv m_env; + fbjsi::Value m_value; // defaults to undefined public: - ReturnValue(JSContextRef ctx) - : m_context(ctx) + ReturnValue(JsiEnv env) + : m_env(env) + { + } + ReturnValue(JsiEnv env, fbjsi::Value&& value) + : m_env(env) + , m_value(std::move(value)) + { + } + ReturnValue(JsiEnv env, const fbjsi::Value& value) + : m_env(env) + , m_value(env, value) { } - void set(const JSValueRef& value) + fbjsi::Value ToValue() && { - m_value = value; + return std::move(m_value); } + + void set(JsiVal value) + { + m_value = std::move(value.get()); + } + void set(const std::string& string) { - m_value = JSValueMakeString(m_context, jsc::String(string)); + m_value = str(m_env, string).get(); } - void set(const char* string) + + void set(const char* c_str) { - m_value = JSValueMakeString(m_context, jsc::String(string)); + if (!c_str) { + set_null(); + } + else { + m_value = str(m_env, c_str).get(); + } } + void set(bool boolean) { - m_value = JSValueMakeBoolean(m_context, boolean); + m_value = fbjsi::Value(boolean); } + void set(double number) { - m_value = JSValueMakeNumber(m_context, number); + m_value = fbjsi::Value(number); } + void set(int32_t number) { - m_value = JSValueMakeNumber(m_context, number); + set(double(number)); } + void set(uint32_t number) { - m_value = JSValueMakeNumber(m_context, number); + set(double(number)); } + void set(realm::Mixed mixed) { - m_value = Value::from_mixed(m_context, nullptr, mixed); + m_value = js::Value::from_mixed(m_env, nullptr, mixed).get(); } + void set_null() { - m_value = JSValueMakeNull(m_context); + m_value = fbjsi::Value::null(); } + + void set_undefined() { - m_value = JSValueMakeUndefined(m_context); + m_value = fbjsi::Value::undefined(); } template void set(const std::optional& value) { if (value) { - set(*value); + set(std::move(*value)); } else { set_undefined(); } } - operator JSValueRef() const + operator JsiVal() const { - return m_value; + return m_env(m_value); } }; diff --git a/src/jsi/jsi_string.hpp b/src/jsi/jsi_string.hpp new file mode 100644 index 0000000000..fa5763b438 --- /dev/null +++ b/src/jsi/jsi_string.hpp @@ -0,0 +1,93 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2021 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. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "jsi_types.hpp" + +namespace realm { +namespace js { + +namespace fbjsi = facebook::jsi; + +template <> +class String { + using StringType = String; + + std::string m_str; + +public: + static bson::Bson to_bson(StringType stringified_ejson) + { + return bson::parse(std::string(std::move(stringified_ejson))); + } + + static std::string from_bson(const bson::Bson& bson) + { + return bson.to_string(); + } + + String(StringData s) + : m_str(s) + { + } + String(std::string&& s) + : m_str(std::move(s)) + { + } + String(const std::string& s) + : m_str(s) + { + } + String(const char* s) + : m_str(s) + { + } + + operator StringData() const + { + return m_str; + } + + operator std::string() && + { + return std::move(m_str); + } + operator std::string() const& + { + return m_str; + } + + fbjsi::String ToString(fbjsi::Runtime* env) + { + return fbjsi::String::createFromUtf8(*env, m_str); + } +}; + +inline fbjsi::PropNameID propName(JsiEnv env, StringData name) +{ + return fbjsi::PropNameID::forUtf8(env, reinterpret_cast(name.data()), name.size()); +} + +inline JsiString str(JsiEnv env, StringData name) +{ + return env(fbjsi::String::createFromUtf8(env, reinterpret_cast(name.data()), name.size())); +} + +} // namespace js +} // namespace realm diff --git a/src/jsi/jsi_types.hpp b/src/jsi/jsi_types.hpp new file mode 100644 index 0000000000..4df20cd449 --- /dev/null +++ b/src/jsi/jsi_types.hpp @@ -0,0 +1,401 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2021 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. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include +#include +#include +#include + +#include + +#define HANDLESCOPE(env) ::facebook::jsi::Scope handle_scope(env); + +#include "js_types.hpp" + + +namespace realm { + +namespace fbjsi = facebook::jsi; + +namespace js { +class JsiVal; +class JsiObj; +class JsiString; +class JsiFunc; + +class JsiEnv { +public: + /*implicit*/ JsiEnv(fbjsi::Runtime& rt) + : m_rt(&rt) + { + } + /*implicit*/ operator fbjsi::Runtime&() const + { + return *m_rt; + } + + fbjsi::Runtime* operator->() const + { + return m_rt; + } + fbjsi::Runtime& get() const + { + return *m_rt; + } + + JsiVal operator()(const fbjsi::Value&) const; + JsiVal operator()(fbjsi::Value&&) const; + JsiObj operator()(const fbjsi::Object&) const; + JsiObj operator()(fbjsi::Object&&) const; + JsiString operator()(const fbjsi::String&) const; + JsiString operator()(fbjsi::String&&) const; + JsiFunc operator()(const fbjsi::Function&) const; + JsiFunc operator()(fbjsi::Function&&) const; + + JsiVal null() const; + JsiVal undefined() const; + + JsiObj global() const; + + /** Warning, this can only appear directly as an argument to callFoo(), not assigned to a variable! */ + const fbjsi::Value* args(const JsiVal* argv, size_t argc, std::vector&& buf = {}) const; + + friend bool operator==(const JsiEnv& a, const JsiEnv& b) + { + return a.m_rt == b.m_rt; + } + + template + JsiObj obj(std::pair... pairs); + +private: + fbjsi::Runtime* m_rt; +}; + +template +class JsiWrap { +public: + JsiWrap(JsiEnv env, T&& val) + : m_env(env) + , m_val(std::move(val)) + { + } + + JsiWrap(JsiWrap&& other) = default; + JsiWrap& operator=(JsiWrap&& other) = default; + + JsiWrap(const JsiWrap& other) + : JsiWrap(CRTP(other.env(), other.get())) + { + } + JsiWrap& operator=(const JsiWrap& other) + { + *this = JsiWrap(other); + } + + T* operator->() + { + return &m_val; + } + const T* operator->() const + { + return &m_val; + } + const T* operator*() const + { + return &m_val; + } + + /*implicit*/ operator const T&() const& + { + return m_val; + } + /*implicit*/ operator T&() & + { + return m_val; + } + /*implicit*/ operator T&&() && + { + return std::move(m_val); + } + + const T& get() const& + { + return m_val; + } + T& get() & + { + return m_val; + } + T&& get() && + { + return std::move(m_val); + } + + const JsiEnv& env() const + { + return m_env; + } + + friend bool operator==(const JsiWrap& a, const JsiWrap& b) + { + REALM_ASSERT_RELEASE(&a.env().get() == &b.env().get()); + return T::strictEquals(a.env(), a.get(), b.get()); + } + +protected: + JsiEnv m_env; + T m_val; +}; + +class JsiString : public JsiWrap { +public: + using JsiWrap::JsiWrap; + JsiString(JsiEnv env, const fbjsi::String& val) + : JsiWrap(env, fbjsi::Value(env, val).getString(env)) + { + } +}; + +class JsiFunc : public JsiWrap { +public: + using JsiWrap::JsiWrap; + JsiFunc(JsiEnv env, const fbjsi::Function& val) + : JsiWrap(env, fbjsi::Value(env, val).getObject(env).getFunction(env)) + { + } +}; + +class JsiObj : public JsiWrap { +public: + using JsiWrap::JsiWrap; + JsiObj(JsiEnv env, const fbjsi::Object& val) + : JsiWrap(env, fbjsi::Value(env, val).getObject(env)) + { + } + /*implicit*/ JsiObj(JsiFunc f) + : JsiWrap(f.env(), std::move(f).get()) + { + } + explicit JsiObj(JsiEnv env) + : JsiWrap(env, fbjsi::Object(env)) + { + } + + bool operator==(const JsiObj& other) const + { + return static_cast(*this) == static_cast(other); + } + + bool operator!=(const JsiObj& other) const + { + return !(*this == other); + } +}; + +class JsiVal : public JsiWrap { +public: + using JsiWrap::JsiWrap; + JsiVal(JsiEnv env, const fbjsi::Value& val) + : JsiWrap(env, fbjsi::Value(env, val)) + { + } + /*implicit*/ JsiVal(JsiString val) + : JsiWrap(val.env(), std::move(val).get()) + { + } + /*implicit*/ JsiVal(JsiFunc val) + : JsiWrap(val.env(), std::move(val).get()) + { + } + /*implicit*/ JsiVal(JsiObj val) + : JsiWrap(val.env(), std::move(val).get()) + { + } + + JsiObj asObject() const& + { + return {env(), get().asObject(env())}; + } + JsiObj asObject() && + { + return {env(), std::move(get()).asObject(env())}; + } +}; + +inline JsiVal JsiEnv::operator()(const fbjsi::Value& val) const +{ + return {*this, val}; +} +inline JsiVal JsiEnv::operator()(fbjsi::Value&& val) const +{ + return {*this, std::move(val)}; +} +inline JsiObj JsiEnv::operator()(const fbjsi::Object& val) const +{ + return {*this, val}; +} +inline JsiObj JsiEnv::operator()(fbjsi::Object&& val) const +{ + return {*this, std::move(val)}; +} +inline JsiString JsiEnv::operator()(const fbjsi::String& val) const +{ + return {*this, val}; +} +inline JsiString JsiEnv::operator()(fbjsi::String&& val) const +{ + return {*this, std::move(val)}; +} +inline JsiFunc JsiEnv::operator()(const fbjsi::Function& val) const +{ + return {*this, val}; +} +inline JsiFunc JsiEnv::operator()(fbjsi::Function&& val) const +{ + return {*this, std::move(val)}; +} + +inline JsiVal JsiEnv::null() const +{ + return {*this, fbjsi::Value::null()}; +} +inline JsiVal JsiEnv::undefined() const +{ + return {*this, fbjsi::Value::undefined()}; +} +inline JsiObj JsiEnv::global() const +{ + return {*this, m_rt->global()}; +} +inline const fbjsi::Value* JsiEnv::args(const JsiVal* argv, size_t argc, std::vector&& buf) const +{ + // Special case for 0 or 1 arguments to avoid any copies and allocations. + if (argc == 0) + return nullptr; + if (argc == 1) + return &argv[0].get(); + + buf.reserve(argc); + for (size_t i = 0; i < argc; i++) { + buf.emplace_back(*this, argv[i]); + } + + return buf.data(); +} + +template +JsiObj JsiEnv::obj(std::pair... pairs) +{ + auto obj = fbjsi::Object(*this); + (..., obj.setProperty(*this, pairs.first, std::move(pairs.second))); + return (*this)(std::move(obj)); +} + +namespace realmjsi { +struct Types { + using Context = JsiEnv; + using GlobalContext = JsiEnv; + using Value = JsiVal; + using Object = JsiObj; + using String = JsiString; + using Function = JsiFunc; + + using JsiFunctionCallback = fbjsi::Value (*)(fbjsi::Runtime& rt, const fbjsi::Value& thisVal, + const fbjsi::Value* args, size_t count); + + using JsiIndexGetterCallback = JsiFunctionCallback; + using JsiIndexSetterCallback = JsiFunctionCallback; + using JsiPropertyGetterCallback = JsiFunctionCallback; + using JsiPropertySetterCallback = JsiFunctionCallback; + + using JsiStringPropertyGetterCallback = JsiFunctionCallback; + using JsiStringPropertySetterCallback = JsiFunctionCallback; + using JsiStringPropertyEnumeratorCallback = JsiFunctionCallback; + + using ConstructorCallback = JsiFunctionCallback; + using FunctionCallback = JsiFunctionCallback; + using PropertyGetterCallback = JsiPropertyGetterCallback; + using PropertySetterCallback = JsiPropertySetterCallback; + using IndexPropertyGetterCallback = JsiIndexGetterCallback; + using IndexPropertySetterCallback = JsiIndexSetterCallback; + + using StringPropertyGetterCallback = fbjsi::Value (*)(fbjsi::Runtime&, const fbjsi::Value&, + const js::String&); + using StringPropertySetterCallback = fbjsi::Value (*)(fbjsi::Runtime&, const fbjsi::Value&, + const js::String&, const fbjsi::Value&); + using StringPropertyEnumeratorCallback = JsiStringPropertyEnumeratorCallback; +}; // struct Types + +template +class ObjectWrap; + +using String = js::String; +using Context = js::Context; +using Value = js::Value; +using Function = js::Function; +using Object = js::Object; +using Exception = js::Exception; +using ReturnValue = js::ReturnValue; + +} // namespace realmjsi + + +template <> +inline realmjsi::Types::Context realmjsi::Context::get_global_context(realmjsi::Types::Context env) +{ + return env; +} + +inline fbjsi::Function globalType(fbjsi::Runtime& env, const char* name) +{ + return env.global().getPropertyAsFunction(env, name); +} + +} // namespace js +} // namespace realm + +// A bit of a hack, but important for usability. +namespace facebook { +namespace jsi { +namespace detail { +template <> +inline Value toValue(Runtime&, const realm::js::JsiVal& val) +{ + return realm::js::JsiVal(val).get(); +} +template <> +inline Value toValue(Runtime&, const realm::js::JsiObj& val) +{ + return realm::js::JsiVal(val).get(); +} +template <> +inline Value toValue(Runtime&, const realm::js::JsiFunc& val) +{ + return realm::js::JsiVal(val).get(); +} +template <> +inline Value toValue(Runtime&, const realm::js::JsiString& val) +{ + return realm::js::JsiVal(val).get(); +} +} // namespace detail +} // namespace jsi +} // namespace facebook diff --git a/src/jsi/jsi_value.hpp b/src/jsi/jsi_value.hpp new file mode 100644 index 0000000000..cb379f0299 --- /dev/null +++ b/src/jsi/jsi_value.hpp @@ -0,0 +1,429 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2021 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. +// +//////////////////////////////////////////////////////////////////////////// + +#pragma once + +#include "jsi_string.hpp" +#include "jsi_types.hpp" +#include "realm/util/to_string.hpp" + +namespace realm { +namespace js { + +namespace fbjsi = facebook::jsi; + +template <> +inline const char* realmjsi::Value::typeof(JsiEnv env, const JsiVal& value) +{ + if (value->isNull()) { + return "null"; + } + if (value->isNumber()) { + return "number"; + } + if (value->isString()) { + return "string"; + } + if (value->isBool()) { + return "boolean"; + } + if (value->isUndefined()) { + return "undefined"; + } + if (value->isObject()) { + return "object"; + } + return "unknown"; +} + +template <> +inline bool realmjsi::Value::is_array(JsiEnv env, const JsiVal& value) +{ + return value->isObject() && value->getObject(env).isArray(env); +} + +template <> +inline bool realmjsi::Value::is_array_buffer(JsiEnv env, const JsiVal& value) +{ + return value->isObject() && value->getObject(env).isArrayBuffer(env); +} + +template <> +inline bool realmjsi::Value::is_array_buffer_view(JsiEnv env, const JsiVal& value) +{ + return globalType(env, "ArrayBuffer").getPropertyAsFunction(env, "isView").call(env, value).getBool(); +} + +template <> +inline bool realmjsi::Value::is_date(JsiEnv env, const JsiVal& value) +{ + return value->isObject() && + value->getObject(env).instanceOf(env, env->global().getPropertyAsFunction(env, "Date")); +} + +template <> +inline bool realmjsi::Value::is_boolean(JsiEnv env, const JsiVal& value) +{ + return value->isBool(); +} + +template <> +inline bool realmjsi::Value::is_constructor(JsiEnv env, const JsiVal& value) +{ + return value->isObject() && value->getObject(env).isFunction(env); +} + +template <> +inline bool realmjsi::Value::is_error(JsiEnv env, const JsiVal& value) +{ + return value->isObject() && + value->getObject(env).instanceOf(env, env->global().getPropertyAsFunction(env, "Error")); +} + +template <> +inline bool realmjsi::Value::is_function(JsiEnv env, const JsiVal& value) +{ + return value->isObject() && value->getObject(env).isFunction(env); +} + +template <> +inline bool realmjsi::Value::is_null(JsiEnv env, const JsiVal& value) +{ + return value->isNull(); +} + +template <> +inline bool realmjsi::Value::is_number(JsiEnv env, const JsiVal& value) +{ + return value->isNumber(); +} + +inline bool is_bson_type(JsiEnv env, const JsiVal& value, std::string type) +{ + if (value->isNull() || value->isUndefined() || !value->isObject()) { + return false; + } + + auto bsonType = value->getObject(env).getProperty(env, "_bsontype"); + if (bsonType.isUndefined()) { + return false; + } + + return fbjsi::Value::strictEquals(env, bsonType, JsiVal(str(env, type))); +} + +template <> +inline bool realmjsi::Value::is_decimal128(JsiEnv env, const JsiVal& value) +{ + return is_bson_type(env, value, "Decimal128"); +} + +template <> +inline bool realmjsi::Value::is_object_id(JsiEnv env, const JsiVal& value) +{ + return is_bson_type(env, value, "ObjectID"); +} + +template <> +inline bool realmjsi::Value::is_object(JsiEnv env, const JsiVal& value) +{ + return value->isObject(); +} + +template <> +inline bool realmjsi::Value::is_string(JsiEnv env, const JsiVal& value) +{ + return value->isString(); +} + +template <> +inline bool realmjsi::Value::is_undefined(JsiEnv env, const JsiVal& value) +{ + return value->isUndefined(); +} + +template <> +inline bool realmjsi::Value::is_binary(JsiEnv env, const JsiVal& value) +{ + return Value::is_array_buffer(env, value) || Value::is_array_buffer_view(env, value); +} + +template <> +inline bool realmjsi::Value::is_valid(const JsiVal& value) +{ + return (*value) != nullptr; +} + +template <> +inline bool realmjsi::Value::is_uuid(JsiEnv env, const JsiVal& value) +{ + return is_bson_type(env, value, "UUID"); +} + +template <> +inline JsiVal realmjsi::Value::from_boolean(JsiEnv env, bool boolean) +{ + return JsiVal(env, boolean); +} + +template <> +inline JsiVal realmjsi::Value::from_null(JsiEnv env) +{ + return env.null(); +} + +template <> +inline JsiVal realmjsi::Value::from_number(JsiEnv env, double number) +{ + return JsiVal(env, number); +} + +template <> +inline JsiVal realmjsi::Value::from_nonnull_string(JsiEnv env, const realmjsi::String& string) +{ + return str(env, StringData(string)); +} + +template <> +inline JsiVal realmjsi::Value::from_nonnull_binary(JsiEnv env, BinaryData data) +{ + fbjsi::ArrayBuffer buffer = + globalType(env, "ArrayBuffer").callAsConstructor(env, double(data.size())).getObject(env).getArrayBuffer(env); + + if (data.size()) { + memcpy(buffer.data(env), data.data(), data.size()); + } + + return env(std::move(buffer)); +} + +template <> +inline JsiVal realmjsi::Value::from_undefined(JsiEnv env) +{ + return env.undefined(); +} + +template <> +inline JsiVal realmjsi::Value::from_uuid(JsiEnv env, const UUID& uuid) +{ + return env(globalType(env, "Realm") + .getPropertyAsFunction(env, "_UUID") + .callAsConstructor(env, str(env, uuid.to_string()))); +} + +template <> +inline bool realmjsi::Value::to_boolean(JsiEnv env, const JsiVal& value) +{ + if (value->isBool()) { + return value->getBool(); + } + + // boolean conversions as specified by + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean ... + + // trivial conversions to false + if (value->isUndefined() || value->isNull()) { + return false; + } + + if (value->isObject()) { + // not null, as checked above + return true; + } + + if (value->isString()) { + // only the empty string is false + return value->toString(env).utf8(env) != ""; + } + + if (value->isNumber()) { + double const dblval = value->asNumber(); + if (std::isnan(dblval)) { + return false; + } + + std::string const stringval = value->toString(env).utf8(env); + return (stringval != "0" && stringval != "-0"); + } + + throw fbjsi::JSError(env, + util::format("TypeError: cannot convert type %1 to boolean", Value::typeof(env, value))); +} + +template <> +inline realmjsi::String realmjsi::Value::to_string(JsiEnv env, const JsiVal& value) +{ + return value->toString(env).utf8(env); +} + +template <> +inline double realmjsi::Value::to_number(JsiEnv env, const JsiVal& value) +{ + double number = std::nan(""); + if (value->isNumber()) { + number = value->asNumber(); + } + else if (value->isString()) { + std::string string = value->toString(env).utf8(env); + try { + number = std::stod(string); + } + catch (std::invalid_argument) { + // The number will remain nan and we defer to the check below to throw an exception. + } + } + else if (is_date(env, value)) { + fbjsi::Object date = value->getObject(env); + number = date.getPropertyAsFunction(env, "getTime").callWithThis(env, date).getNumber(); + } + if (std::isnan(number)) { + throw std::invalid_argument( + util::format("Value '%1' not convertible to a number.", (std::string)to_string(env, value))); + } + + return number; +} + +template <> +inline OwnedBinaryData realmjsi::Value::to_binary(JsiEnv env, const JsiVal& value) +{ + auto obj = value->asObject(env); + if (obj.isArrayBuffer(env)) { + auto buf = std::move(obj).getArrayBuffer(env); + return OwnedBinaryData(reinterpret_cast(buf.data(env)), buf.length(env)); + } + + if (is_array_buffer_view(env, value)) { + auto buffer = obj.getPropertyAsObject(env, "buffer").getArrayBuffer(env); + size_t byteOffset = static_cast(obj.getProperty(env, "byteOffset").asNumber()); + size_t byteLength = static_cast(obj.getProperty(env, "byteLength").asNumber()); + return OwnedBinaryData(reinterpret_cast(buffer.data(env) + byteOffset), byteLength); + } + + throw std::runtime_error("Can only convert ArrayBuffer and ArrayBufferView objects to binary"); +} + +/** + * @brief convert a JSI value to an object + * Will try to convert a given value to a JavaScript object according to + * https://tc39.es/ecma262/#sec-toobject. Most primitive types will be wrapped + * in their corresponding object types (e.g., string -> String). + * + * @param env JSI runtime environment + * @param value JSI value that will be converted to object + * @return JsiObj + */ +template <> +inline JsiObj realmjsi::Value::to_object(JsiEnv env, JsiVal const& value) +{ + if (value->isObject()) { + return env(value->asObject(env)); + } + + // trivial non-conversions + if (value->isNull() || value->isUndefined()) { + throw fbjsi::JSError(env, util::format("TypeError: cannot convert '%1' to object", + realmjsi::Value::typeof(env, value))); // throw TypeError + } + + // use JavaScript's `Object()` to wrap types in their corresponding object types + auto objectCtor = env->global().getPropertyAsFunction(env, "Object"); + fbjsi::Value wrappedValue = objectCtor.callAsConstructor(env, value); + if (!wrappedValue.isObject()) { + throw fbjsi::JSError( + env, util::format("TypeError: cannot wrap %1 in Object", realmjsi::Value::typeof(env, value))); + } + return env(wrappedValue.asObject(env)); +} + +template <> +inline JsiObj realmjsi::Value::to_array(JsiEnv env, const JsiVal& value) +{ + return to_object(env, value); +} + +template <> +inline JsiFunc realmjsi::Value::to_function(JsiEnv env, const JsiVal& value) +{ + return env(value->asObject(env).asFunction(env)); +} + +template <> +inline JsiFunc realmjsi::Value::to_constructor(JsiEnv env, const JsiVal& value) +{ + return to_function(env, value); +} + +template <> +inline JsiObj realmjsi::Value::to_date(JsiEnv env, const JsiVal& value) +{ + if (value->isString()) { + return env(globalType(env, "Date").callAsConstructor(env, value).asObject(env)); + } + + return to_object(env, value); +} + +template <> +inline JsiVal realmjsi::Value::from_decimal128(JsiEnv env, const Decimal128& number) +{ + if (number.is_null()) { + return env(fbjsi::Value::null()); + } + + return env(globalType(env, "Realm") + .getPropertyAsObject(env, "_Decimal128") + .getPropertyAsFunction(env, "fromString") + .call(env, str(env, number.to_string()))); +} + +template <> +inline Decimal128 realmjsi::Value::to_decimal128(JsiEnv env, const JsiVal& value) +{ + return Decimal128(value->toString(env).utf8(env)); +} + +template <> +inline JsiVal realmjsi::Value::from_object_id(JsiEnv env, const ObjectId& objectId) +{ + return env(globalType(env, "Realm") + .getPropertyAsFunction(env, "_ObjectId") + .callAsConstructor(env, str(env, objectId.to_string()))); +} + +template <> +inline ObjectId realmjsi::Value::to_object_id(JsiEnv env, const JsiVal& value) +{ + auto objectId = value->asObject(env); + return ObjectId(objectId.getPropertyAsFunction(env, "toHexString") + .callWithThis(env, objectId) + .getString(env) + .utf8(env) + .c_str()); +} + +template <> +inline UUID realmjsi::Value::to_uuid(JsiEnv env, const JsiVal& value) +{ + auto uuid = value->asObject(env); + return UUID( + uuid.getPropertyAsFunction(env, "toHexString").callWithThis(env, uuid).getString(env).utf8(env).c_str()); +} + +} // namespace js +} // namespace realm diff --git a/src/node/node_class.hpp b/src/node/node_class.hpp index 18562f37ac..01c1f31fdc 100644 --- a/src/node/node_class.hpp +++ b/src/node/node_class.hpp @@ -52,6 +52,7 @@ namespace node { Napi::FunctionReference ObjectGetOwnPropertyDescriptor; node::Protected ExternalSymbol; +Napi::FunctionReference ObjectCreate; Napi::FunctionReference ObjectSetPrototypeOf; Napi::FunctionReference GlobalProxy; Napi::FunctionReference FunctionBind; @@ -63,6 +64,11 @@ static void node_class_init(Napi::Env env) ObjectSetPrototypeOf = Napi::Persistent(setPrototypeOf); ObjectSetPrototypeOf.SuppressDestruct(); + auto create = env.Global().Get("Object").As().Get("create").As(); + ObjectCreate = Napi::Persistent(create); + ObjectCreate.SuppressDestruct(); + + auto getOwnPropertyDescriptor = env.Global().Get("Object").As().Get("getOwnPropertyDescriptor").As(); ObjectGetOwnPropertyDescriptor = Napi::Persistent(getOwnPropertyDescriptor); @@ -205,7 +211,7 @@ class ObjectWrap { static bool is_instance(Napi::Env env, const Napi::Object& object); static Internal* get_internal(Napi::Env env, const Napi::Object& object); - static void set_internal(Napi::Env env, const Napi::Object& object, Internal* data); + static void set_internal(Napi::Env env, Napi::Object& object, Internal* data); static Napi::Value constructor_callback(const Napi::CallbackInfo& info); static bool has_native_method(const std::string& name); @@ -1274,10 +1280,12 @@ Napi::Object ObjectWrap::create_instance_by_schema(Napi::Env env, Nap } Napi::External externalValue = Napi::External::New(env, internal, internal_finalizer); - instance = schemaObjectConstructor.New({}); + Napi::Object constructorPrototype = schemaObjectConstructor.Get("prototype").As(); + instance = ObjectCreate.Call({constructorPrototype}).As(); instance.Set(externalSymbol, externalValue); } else { + Napi::Object constructorPrototype = constructor.Get("prototype").As(); // creating a RealmObject with user defined constructor bool schemaExists = schemaObjects->count(schemaName); @@ -1297,8 +1305,7 @@ Napi::Object ObjectWrap::create_instance_by_schema(Napi::Env env, Nap if (schemaExists) { schemaObjectType = schemaObjects->at(schemaName); schemaObjectConstructor = schemaObjectType->constructor.Value(); - - instance = schemaObjectConstructor.New({}); + instance = ObjectCreate.Call({constructorPrototype}).As(); Napi::External externalValue = Napi::External::New(env, internal, internal_finalizer); instance.Set(externalSymbol, externalValue); @@ -1307,52 +1314,18 @@ Napi::Object ObjectWrap::create_instance_by_schema(Napi::Env env, Nap } schemaObjectConstructor = constructor; - Napi::Object constructorPrototype = constructor.Get("prototype").As(); // get all properties from the schema std::vector properties = create_napi_property_descriptors(env, constructorPrototype, schema, false /*redefine*/); - Napi::Function realmObjectClassConstructor = ObjectWrap::create_constructor(env); - bool isInstanceOfRealmObjectClass = constructorPrototype.InstanceOf(realmObjectClassConstructor); - - // Skip if the user defined constructor inherited the RealmObjectClass. All RealmObjectClass members are - // available already. - if (!isInstanceOfRealmObjectClass) { - // setup all RealmObjectClass methods to the prototype of the object - for (auto& pair : s_class.methods) { - // don't redefine if exists - if (!constructorPrototype.HasOwnProperty(pair.first)) { - auto descriptor = Napi::PropertyDescriptor::Function( - env, constructorPrototype, Napi::String::New(env, pair.first) /*name*/, &method_callback, - napi_default | realm::js::PropertyAttributes::DontEnum, (void*)pair.second /*callback*/); - properties.push_back(descriptor); - } - } - - for (auto& pair : s_class.properties) { - // don't redefine if exists - if (!constructorPrototype.HasOwnProperty(pair.first)) { - napi_property_attributes napi_attributes = - napi_default | - (realm::js::PropertyAttributes::DontEnum | realm::js::PropertyAttributes::DontDelete); - auto descriptor = Napi::PropertyDescriptor::Accessor( - Napi::String::New(env, pair.first) /*name*/, napi_attributes, - (void*)&pair.second /*callback*/); - properties.push_back(descriptor); - } - } - } - // define the properties on the prototype of the schema object constructor if (properties.size() > 0) { constructorPrototype.DefineProperties(properties); } - instance = schemaObjectConstructor.New({}); - if (!instance.InstanceOf(schemaObjectConstructor)) { - throw Napi::Error::New(env, "Realm object constructor must not return another value"); - } + instance = ObjectCreate.Call({constructorPrototype}).As(); + Napi::External externalValue = Napi::External::New(env, internal, internal_finalizer); instance.Set(externalSymbol, externalValue); @@ -1411,8 +1384,7 @@ typename ClassType::Internal* ObjectWrap::get_internal(Napi::Env env, } template -void ObjectWrap::set_internal(Napi::Env env, const Napi::Object& object, - typename ClassType::Internal* internal) +void ObjectWrap::set_internal(Napi::Env env, Napi::Object& object, typename ClassType::Internal* internal) { bool isRealmObjectClass = std::is_same>::value; if (isRealmObjectClass) { @@ -1439,11 +1411,6 @@ Napi::Value ObjectWrap::constructor_callback(const Napi::CallbackInfo return scope.Escape(env.Null()); // return a value to comply with Napi::FunctionCallback } else { - bool isRealmObjectClass = std::is_same>::value; - if (isRealmObjectClass) { - return scope.Escape(env.Null()); // return a value to comply with Napi::FunctionCallback - } - throw Napi::Error::New(env, "Illegal constructor"); } } diff --git a/src/node/node_object.hpp b/src/node/node_object.hpp index 0b2a916934..adca80842e 100644 --- a/src/node/node_object.hpp +++ b/src/node/node_object.hpp @@ -224,8 +224,7 @@ inline typename ClassType::Internal* node::Object::get_internal(Napi::Env env, c template <> template -inline void node::Object::set_internal(Napi::Env env, const Napi::Object& object, - typename ClassType::Internal* internal) +inline void node::Object::set_internal(Napi::Env env, Napi::Object& object, typename ClassType::Internal* internal) { return node::ObjectWrap::set_internal(env, object, internal); } diff --git a/src/node/node_return_value.hpp b/src/node/node_return_value.hpp index f9b04f14df..884a24a23c 100644 --- a/src/node/node_return_value.hpp +++ b/src/node/node_return_value.hpp @@ -43,7 +43,7 @@ class ReturnValue { { } - Napi::Value ToValue() + Napi::Value ToValue() const { // guard check. env.Empty() values cause node to fail in obscure places, so return undefined instead if (m_value.IsEmpty()) { @@ -119,6 +119,11 @@ class ReturnValue { set_undefined(); } } + + operator Napi::Value() const + { + return ToValue(); + } }; } // namespace js diff --git a/tests/.lock b/tests/.lock deleted file mode 100644 index 88ead44965..0000000000 Binary files a/tests/.lock and /dev/null differ diff --git a/tests/js/array-buffer-tests.js b/tests/js/array-buffer-tests.js index 8fd0b8d0a9..ebf6c80107 100644 --- a/tests/js/array-buffer-tests.js +++ b/tests/js/array-buffer-tests.js @@ -31,38 +31,8 @@ module.exports = { testABufferView() { let realm = new Realm({ schema: [SingleSchema] }); const view = new Uint8Array([ - 0xd8, - 0x21, - 0xd6, - 0xe8, - 0x00, - 0x57, - 0xbc, - 0xb2, - 0x6a, - 0x15, - 0x77, - 0x30, - 0xac, - 0x77, - 0x96, - 0xd9, - 0x67, - 0x1e, - 0x40, - 0xa7, - 0x6d, - 0x52, - 0x83, - 0xda, - 0x07, - 0x29, - 0x9c, - 0x70, - 0x38, - 0x48, - 0x4e, - 0xff, + 0xd8, 0x21, 0xd6, 0xe8, 0x00, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9, 0x67, 0x1e, 0x40, + 0xa7, 0x6d, 0x52, 0x83, 0xda, 0x07, 0x29, 0x9c, 0x70, 0x38, 0x48, 0x4e, 0xff, ]); realm.write(() => realm.create(SingleSchema.name, { a: view })); @@ -83,38 +53,8 @@ module.exports = { testABufferDataView() { let realm = new Realm({ schema: [SingleSchema] }); const view = new Uint8Array([ - 0xd8, - 0x21, - 0xd6, - 0xe8, - 0x00, - 0x57, - 0xbc, - 0xb2, - 0x6a, - 0x15, - 0x77, - 0x30, - 0xac, - 0x77, - 0x96, - 0xd9, - 0x67, - 0x1e, - 0x40, - 0xa7, - 0x6d, - 0x52, - 0x83, - 0xda, - 0x07, - 0x29, - 0x9c, - 0x70, - 0x38, - 0x48, - 0x4e, - 0xff, + 0xd8, 0x21, 0xd6, 0xe8, 0x00, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9, 0x67, 0x1e, 0x40, + 0xa7, 0x6d, 0x52, 0x83, 0xda, 0x07, 0x29, 0x9c, 0x70, 0x38, 0x48, 0x4e, 0xff, ]); realm.write(() => realm.create(SingleSchema.name, { a: new DataView(view.buffer) })); @@ -137,38 +77,8 @@ module.exports = { let array_buffer = new ArrayBuffer(32); const view = new Uint8Array(array_buffer); view.set([ - 0xd8, - 0x21, - 0xd6, - 0xe8, - 0x00, - 0x57, - 0xbc, - 0xb2, - 0x6a, - 0x15, - 0x77, - 0x30, - 0xac, - 0x77, - 0x96, - 0xd9, - 0x67, - 0x1e, - 0x40, - 0xa7, - 0x6d, - 0x52, - 0xca, - 0xfe, - 0xba, - 0xbe, - 0x9c, - 0x70, - 0x38, - 0x48, - 0x4e, - 0xff, + 0xd8, 0x21, 0xd6, 0xe8, 0x00, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9, 0x67, 0x1e, 0x40, + 0xa7, 0x6d, 0x52, 0xca, 0xfe, 0xba, 0xbe, 0x9c, 0x70, 0x38, 0x48, 0x4e, 0xff, ]); realm.write(() => realm.create(SingleSchema.name, { a: array_buffer })); diff --git a/tests/js/asserts.js b/tests/js/asserts.js index 60dd173530..b8ec644767 100644 --- a/tests/js/asserts.js +++ b/tests/js/asserts.js @@ -177,7 +177,7 @@ module.exports = { func(); } catch (e) { caught = true; - if (!e.message.includes(expectedMessage)) { + if (!e.message.includes(expectedMessage) && !e.name.includes(expectedMessage)) { throw new TestFailureError( `Expected exception "${expectedMessage}" not thrown - instead caught: "${e}"`, depth, @@ -189,6 +189,7 @@ module.exports = { throw new TestFailureError(`Expected exception "${expectedMessage}" not thrown`, depth); } }, + assertThrowsAsyncContaining: async function (func, expectedMessage, depth) { let caught = false; try { diff --git a/tests/js/dictionary-tests.js b/tests/js/dictionary-tests.js index 70ca4a31ca..b4d906c134 100644 --- a/tests/js/dictionary-tests.js +++ b/tests/js/dictionary-tests.js @@ -162,14 +162,13 @@ module.exports = { TestCase.assertEqual(data.a.y, 2, "Should be an equals to a.y = 2"); TestCase.assertEqual(data.a.z, 4, "Should be an equals to a.z = 4"); - let err = new Error("Property must be of type 'number', got (error)"); - TestCase.assertThrowsException( + TestCase.assertThrowsContaining( () => realm.write(() => realm.create(DictIntSchema.name, { a: { c: "error" } })), - err, + "Property must be of type 'number', got (error)", ); - TestCase.assertThrowsException( + TestCase.assertThrowsContaining( () => realm.write(() => (data.a = "cc")), - new Error("Dictionary.a must be of type 'number{}', got 'string' ('cc')"), + "Dictionary.a must be of type 'number{}', got 'string' ('cc')", ); realm.close(); @@ -182,13 +181,9 @@ module.exports = { a: "wwwww{}", }, }; - let err = new Error( - "Schema validation failed due to the following errors:\n- Property 'Dictionary.a' of type 'dictionary' has unknown object type 'wwwww'", - ); - let _defer = () => { - let r = new Realm({ schema: [DictWrongSchema] }); - }; - TestCase.assertThrowsException(_defer, err); + TestCase.assertThrowsContaining(() => { + new Realm({ schema: [DictWrongSchema] }); + }, "Schema validation failed due to the following errors:\n- Property 'Dictionary.a' of type 'dictionary' has unknown object type 'wwwww'"); }, testDictionaryMutability() { @@ -746,8 +741,11 @@ module.exports = { testDictionaryErrorHandling() { let realm = new Realm({ schema: [DictSchema] }); - let err = new Error("Only Realm instances are supported."); - TestCase.assertThrowsException(() => realm.write(() => realm.create(DictSchema.name, { a: { x: {} } })), err); + TestCase.assertThrowsContaining(() => { + realm.write(() => { + realm.create(DictSchema.name, { a: { x: {} } }); + }); + }, "Only Realm instances are supported."); realm.write(() => realm.create(DictSchema.name, { a: { x: null } })); let data = realm.objects(DictSchema.name)[0].a; TestCase.assertEqual(data.x, null, "Should be an equals to mutable.x = null"); diff --git a/tests/js/list-tests.js b/tests/js/list-tests.js index 3f3103918e..0db767388e 100644 --- a/tests/js/list-tests.js +++ b/tests/js/list-tests.js @@ -188,12 +188,14 @@ module.exports = { TestCase.assertEqual(obj.arrayCol[1].doubleCol, 4); TestCase.assertEqual(obj.arrayCol[2], undefined); TestCase.assertEqual(obj.arrayCol[-1], undefined); + TestCase.assertEqual(obj.arrayCol[""], undefined); TestCase.assertEqual(obj.arrayCol["foo"], undefined); TestCase.assertEqual(obj.arrayCol1[0].doubleCol, 5); TestCase.assertEqual(obj.arrayCol1[1].doubleCol, 6); TestCase.assertEqual(obj.arrayCol1[2], undefined); TestCase.assertEqual(obj.arrayCol1[-1], undefined); + TestCase.assertEqual(obj.arrayCol1[""], undefined); TestCase.assertEqual(obj.arrayCol1["foo"], undefined); for (let field of prim.keys()) { @@ -1840,58 +1842,42 @@ module.exports = { testClassObjectCreation: function () { class TodoItem extends Realm.Object { - constructor(description) { - super(); - this.id = new ObjectId(); - this.description = description; - this.done = false; + constructor(realm, description) { + super(realm, { done: false, description }); } } TodoItem.schema = { name: "TodoItem", properties: { - id: "objectId", description: "string", done: { type: "bool", default: false }, deadline: "date?", }, - primaryKey: "id", }; class TodoList extends Realm.Object { - constructor(name) { - super(); - this.id = new ObjectId(); - this.name = name; - this.items = []; + constructor(realm, name) { + super(realm, { name }); } } TodoList.schema = { name: "TodoList", properties: { - id: "objectId", name: "string", items: "TodoItem[]", }, - primaryKey: "id", }; const realm = new Realm({ schema: [TodoList, TodoItem] }); realm.write(() => { const list = realm.create(TodoList, { - id: new ObjectId(), name: "MyTodoList", }); - TestCase.assertThrowsContaining(() => { - list.items.push(new TodoItem("Fix that bug")); - }, "Cannot reference a detached instance of Realm.Object"); - - TestCase.assertThrowsContaining(() => { - realm.create(TodoItem, new TodoItem("Fix that bug")); - }, "Cannot create an object from a detached Realm.Object instance"); + list.items.push(new TodoItem(realm, "Fix that bug")); + realm.create(TodoItem, new TodoItem(realm, "Fix that bug")); }); }, }; diff --git a/tests/js/mixed-tests.js b/tests/js/mixed-tests.js index a2fdaa28f7..2d6890f232 100644 --- a/tests/js/mixed-tests.js +++ b/tests/js/mixed-tests.js @@ -166,9 +166,9 @@ module.exports = { testMixedWrongType() { let realm = new Realm({ schema: [SingleSchema] }); - TestCase.assertThrowsException( + TestCase.assertThrowsContaining( () => realm.write(() => realm.create(SingleSchema.name, { a: Object.create({}) })), - new Error("Only Realm instances are supported."), + "Only Realm instances are supported.", ); }, @@ -211,12 +211,11 @@ module.exports = { TestCase.assertEqual(objectsBefore.length, 0); // check if the understandable error message is thrown - const error = new Error("A mixed property cannot contain an array of values."); - TestCase.assertThrowsException(() => { + TestCase.assertThrowsContaining(() => { realm.write(() => { realm.create("MixedClass", { value: [123, false, "hello"] }); }); - }, error); + }, "A mixed property cannot contain an array of values."); // verify that the transaction has been rolled back const objectsAfter = realm.objects(MixedSchema.name); diff --git a/tests/js/object-tests.js b/tests/js/object-tests.js index 839f890c5a..815e5ccdc5 100644 --- a/tests/js/object-tests.js +++ b/tests/js/object-tests.js @@ -24,38 +24,8 @@ const schemas = require("./schemas"); const buffer = require("buffer"); const RANDOM_DATA = new Uint8Array([ - 0xd8, - 0x21, - 0xd6, - 0xe8, - 0x00, - 0x57, - 0xbc, - 0xb2, - 0x6a, - 0x15, - 0x77, - 0x30, - 0xac, - 0x77, - 0x96, - 0xd9, - 0x67, - 0x1e, - 0x40, - 0xa7, - 0x6d, - 0x52, - 0x83, - 0xda, - 0x07, - 0x29, - 0x9c, - 0x70, - 0x38, - 0x48, - 0x4e, - 0xff, + 0xd8, 0x21, 0xd6, 0xe8, 0x00, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9, 0x67, 0x1e, 0x40, + 0xa7, 0x6d, 0x52, 0x83, 0xda, 0x07, 0x29, 0x9c, 0x70, 0x38, 0x48, 0x4e, 0xff, ]); const allTypesValues = { @@ -436,6 +406,63 @@ module.exports = { }); }, + testObjectConversion: function () { + const realm = new Realm({ schema: [schemas.TestObject] }); + TestCase.assertInstanceOf( + realm.__to_object("This is a string"), + String, + "__to_object(string) should return String Object", + ); + TestCase.assertTrue( + realm.__to_object("Foo") == String("Foo"), + '__to_object(string("Foo")) should return String("Foo") Object', + ); + TestCase.assertInstanceOf(realm.__to_object(12345), Number, "__to_object(int) should return Number Object"); + TestCase.assertTrue( + realm.__to_object(12345) == Number(12345), + "__to_object(int(12345)) should return Number(12345) Object", + ); + TestCase.assertInstanceOf(realm.__to_object(false), Boolean, "__to_object(bool) should return Boolean Object"); + TestCase.assertTrue( + realm.__to_object(false) == Boolean(false), + "__to_object(bool(false)) should return Boolean(false) Object", + ); + TestCase.assertInstanceOf(realm.__to_object(new Date()), Date, "__to_object(Date) should return Date Object"); + + TestCase.assertThrowsContaining(() => { + realm.__to_object(null); + }, "TypeError"); + + TestCase.assertThrowsContaining(() => { + realm.__to_object(undefined); + }, "TypeError"); + + realm.close(); + }, + + // tests conversion of various types to boolean as specified in + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean + // it resides here for lack of a better place, and because the direct type conversion + // operations have been made available through the Realm object + testBooleanConversion: function () { + const realm = new Realm({ schema: [schemas.TestObject] }); + TestCase.assertEqual(realm.__to_boolean(""), false, '__to_boolean("") should return false'); + TestCase.assertEqual(realm.__to_boolean(0), false, "__to_boolean(0) should return false"); + TestCase.assertEqual(realm.__to_boolean(-0), false, "__to_boolean(-0) should return false"); + TestCase.assertEqual(realm.__to_boolean(null), false, "__to_boolean(null) should return false"); + TestCase.assertEqual(realm.__to_boolean(false), false, "__to_boolean(false) should return false"); + TestCase.assertEqual(realm.__to_boolean(NaN), false, "__to_boolean(NaN) should return false"); + TestCase.assertEqual(realm.__to_boolean(undefined), false, "__to_boolean(undefined) should return false"); + + TestCase.assertEqual(realm.__to_boolean("false"), true, '__to_boolean("false") should return true'); + TestCase.assertEqual(realm.__to_boolean(1), true, "__to_boolean(1) should return true"); + TestCase.assertEqual(realm.__to_boolean(-1), true, "__to_boolean(-1) should return true"); + TestCase.assertEqual(realm.__to_boolean([]), true, "__to_boolean([]) should return true"); + TestCase.assertEqual(realm.__to_boolean(Object()), true, "__to_boolean(Object()) should return true"); + + realm.close(); + }, + testObjectSchema: function () { const realm = new Realm({ schema: [schemas.TestObject] }); var obj; @@ -845,8 +872,8 @@ module.exports = { }); // property that does not exist - TestCase.assertThrowsException(() => { + TestCase.assertThrowsContaining(() => { obj.getPropertyType("foo"); - }, new Error("No such property: foo")); + }, "No such property: foo"); }, }; diff --git a/tests/js/realm-tests.js b/tests/js/realm-tests.js index efbc908ed1..b2ebd3644f 100644 --- a/tests/js/realm-tests.js +++ b/tests/js/realm-tests.js @@ -88,8 +88,8 @@ module.exports = { let constructorCalled = false; //test class syntax support class Car extends Realm.Object { - constructor() { - super(); + constructor(realm) { + super(realm); constructorCalled = true; } } @@ -125,28 +125,10 @@ module.exports = { Object.setPrototypeOf(Car2.prototype, Realm.Object.prototype); Object.setPrototypeOf(Car2, Realm.Object); - //test class syntax support without extending Realm.Object - let car3ConstructorCalled = false; - class Car3 { - constructor() { - car3ConstructorCalled = true; - } - } - - Car3.schema = { - name: "Car3", - properties: { - make: "string", - model: "string", - otherType: { type: "string", mapTo: "type", optional: true }, - kilometers: { type: "int", default: 0 }, - }, - }; - - let realm = new Realm({ schema: [Car, Car2, Car3] }); + let realm = new Realm({ schema: [Car, Car2] }); realm.write(() => { let car = realm.create("Car", { make: "Audi", model: "A4", kilometers: 24 }); - TestCase.assertTrue(constructorCalled); + TestCase.assertFalse(constructorCalled); TestCase.assertEqual(car.make, "Audi"); TestCase.assertEqual(car.model, "A4"); TestCase.assertEqual(car.kilometers, 24); @@ -162,34 +144,25 @@ module.exports = { constructorCalled = false; let car1 = realm.create("Car", { make: "VW", model: "Touareg", kilometers: 13 }); - TestCase.assertTrue(constructorCalled); + TestCase.assertFalse(constructorCalled); TestCase.assertEqual(car1.make, "VW"); TestCase.assertEqual(car1.model, "Touareg"); TestCase.assertEqual(car1.kilometers, 13); TestCase.assertInstanceOf(car1, Realm.Object, "car1 not an instance of Realm.Object"); let car2 = realm.create("Car2", { make: "Audi", model: "A4", kilometers: 24 }); - TestCase.assertTrue(calledAsConstructor); + TestCase.assertFalse(calledAsConstructor); TestCase.assertEqual(car2.make, "Audi"); TestCase.assertEqual(car2.model, "A4"); TestCase.assertEqual(car2.kilometers, 24); TestCase.assertInstanceOf(car2, Realm.Object, "car2 not an instance of Realm.Object"); let car2_1 = realm.create("Car2", { make: "VW", model: "Touareg", kilometers: 13 }); - TestCase.assertTrue(calledAsConstructor); + TestCase.assertFalse(calledAsConstructor); TestCase.assertEqual(car2_1.make, "VW"); TestCase.assertEqual(car2_1.model, "Touareg"); TestCase.assertEqual(car2_1.kilometers, 13); TestCase.assertInstanceOf(car2_1, Realm.Object, "car2_1 not an instance of Realm.Object"); - - let car3 = realm.create("Car3", { make: "Audi", model: "A4", kilometers: 24 }); - TestCase.assertTrue(car3ConstructorCalled); - TestCase.assertEqual(car3.make, "Audi"); - TestCase.assertEqual(car3.model, "A4"); - TestCase.assertEqual(car3.kilometers, 24); - - //methods from Realm.Objects should be present - TestCase.assertDefined(car3.addListener); }); realm.close(); }, @@ -1176,6 +1149,7 @@ module.exports = { intCol: "int", }, }; + Object.setPrototypeOf(CustomObject, Realm.Object); function InvalidObject() { return {}; @@ -1191,26 +1165,25 @@ module.exports = { intCol: "int", }, }; + Object.setPrototypeOf(InvalidObject, Realm.Object); let realm = new Realm({ schema: [CustomObject, InvalidObject] }); realm.write(() => { let object = realm.create("CustomObject", { intCol: 1 }); TestCase.assertTrue(object instanceof CustomObject); TestCase.assertTrue(Object.getPrototypeOf(object) == CustomObject.prototype); - TestCase.assertEqual(customCreated, 1); + TestCase.assertEqual(customCreated, 0); // Should be able to create object by passing in constructor. object = realm.create(CustomObject, { intCol: 2 }); TestCase.assertTrue(object instanceof CustomObject); TestCase.assertTrue(Object.getPrototypeOf(object) == CustomObject.prototype); - TestCase.assertEqual(customCreated, 2); + TestCase.assertEqual(customCreated, 0); }); - TestCase.assertThrowsContaining(() => { - realm.write(() => { - realm.create("InvalidObject", { intCol: 1 }); - }); - }, "Realm object constructor must not return another value"); + realm.write(() => { + realm.create("InvalidObject", { intCol: 1 }); + }); // Only the original constructor should be valid. function InvalidCustomObject() {} @@ -1238,6 +1211,7 @@ module.exports = { intCol: "int", }, }; + Object.setPrototypeOf(CustomObject, Realm.Object); let realm = new Realm({ schema: [CustomObject] }); realm.write(() => { @@ -1247,6 +1221,7 @@ module.exports = { function NewCustomObject() {} NewCustomObject.schema = CustomObject.schema; + Object.setPrototypeOf(NewCustomObject, Realm.Object); realm = new Realm({ schema: [NewCustomObject] }); realm.write(() => { @@ -1617,22 +1592,22 @@ module.exports = { testErrorMessageFromInvalidWrite: function () { const realm = new Realm({ schema: [schemas.PersonObject] }); - TestCase.assertThrowsException(() => { + TestCase.assertThrowsContaining(() => { realm.write(() => { const p1 = realm.create("PersonObject", { name: "Ari", age: 10 }); p1.age = "Ten"; }); - }, new Error("PersonObject.age must be of type 'number', got 'string' ('Ten')")); + }, "PersonObject.age must be of type 'number', got 'string' ('Ten')"); }, testErrorMessageFromInvalidCreate: function () { const realm = new Realm({ schema: [schemas.PersonObject] }); - TestCase.assertThrowsException(() => { + TestCase.assertThrowsContaining(() => { realm.write(() => { - const p1 = realm.create("PersonObject", { name: "Ari", age: "Ten" }); + realm.create("PersonObject", { name: "Ari", age: "Ten" }); }); - }, new Error("PersonObject.age must be of type 'number', got 'string' ('Ten')")); + }, "PersonObject.age must be of type 'number', got 'string' ('Ten')"); }, testValidTypesForListProperties: function () { diff --git a/tests/js/results-tests.js b/tests/js/results-tests.js index fed4163582..af261d82dc 100644 --- a/tests/js/results-tests.js +++ b/tests/js/results-tests.js @@ -880,38 +880,8 @@ module.exports = { const N = 5; var RANDOM_DATA = new Uint8Array([ - 0xd8, - 0x21, - 0xd6, - 0xe8, - 0x00, - 0x57, - 0xbc, - 0xb2, - 0x6a, - 0x15, - 0x77, - 0x30, - 0xac, - 0x77, - 0x96, - 0xd9, - 0x67, - 0x1e, - 0x40, - 0xa7, - 0x6d, - 0x52, - 0x83, - 0xda, - 0x07, - 0x29, - 0x9c, - 0x70, - 0x38, - 0x48, - 0x4e, - 0xff, + 0xd8, 0x21, 0xd6, 0xe8, 0x00, 0x57, 0xbc, 0xb2, 0x6a, 0x15, 0x77, 0x30, 0xac, 0x77, 0x96, 0xd9, 0x67, 0x1e, 0x40, + 0xa7, 0x6d, 0x52, 0x83, 0xda, 0x07, 0x29, 0x9c, 0x70, 0x38, 0x48, 0x4e, 0xff, ]); var realm = new Realm({ schema: [schemas.NullableBasicTypes] }); diff --git a/tests/package-lock.json b/tests/package-lock.json index 4837e67714..a2c2e7dcc1 100644 --- a/tests/package-lock.json +++ b/tests/package-lock.json @@ -31,7 +31,7 @@ }, "..": { "name": "realm", - "version": "10.19.5", + "version": "11.0.0-rc.1", "hasInstallScript": true, "license": "See the actual license in the file LICENSE", "dependencies": { @@ -44,7 +44,7 @@ "fs-extra": "^4.0.3", "ini": "^1.3.7", "node-addon-api": "4.2.0", - "node-fetch": "^3.2.6", + "node-fetch": "^3.2.10", "node-machine-id": "^1.1.10", "prebuild-install": "^7.0.1", "progress": "^2.0.3", @@ -84,7 +84,7 @@ "mockery": "^2.0.0", "prebuild": "^10.0.1", "prettier": "^2.0.4", - "react-native": "^0.69.1", + "react-native": "0.70.0", "rimraf": "^2.6.3", "semver": "^5.6.0", "shelljs": "^0.8.5", @@ -97,7 +97,7 @@ "npm": ">=7" }, "peerDependencies": { - "react-native": ">=0.60" + "react-native": ">=0.70.0" }, "peerDependenciesMeta": { "react-native": { @@ -15467,14 +15467,14 @@ "license-checker": "^8.0.3", "mockery": "^2.0.0", "node-addon-api": "4.2.0", - "node-fetch": "^3.2.6", + "node-fetch": "^3.2.10", "node-machine-id": "^1.1.10", "prebuild": "^10.0.1", "prebuild-install": "^7.0.1", "prettier": "^2.0.4", "progress": "^2.0.3", "prop-types": "^15.6.2", - "react-native": "^0.69.1", + "react-native": "0.70.0", "realm-network-transport": "^0.7.2", "request": "^2.88.0", "rimraf": "^2.6.3", diff --git a/tests/spec/helpers/reporters.js b/tests/spec/helpers/reporters.js index f7090c8e0a..f9906c27b4 100644 --- a/tests/spec/helpers/reporters.js +++ b/tests/spec/helpers/reporters.js @@ -31,5 +31,8 @@ jasmine.getEnv().addReporter( spec: { displayPending: true, }, + summary: { + displayStacktrace: true, + }, }), ); diff --git a/types/app.d.ts b/types/app.d.ts index edfdc2d25d..503d29577b 100644 --- a/types/app.d.ts +++ b/types/app.d.ts @@ -404,7 +404,7 @@ declare namespace Realm { class User< FunctionsFactoryType = DefaultFunctionsFactory, CustomDataType = SimpleObject, - UserProfileDataType = DefaultUserProfileData + UserProfileDataType = DefaultUserProfileData, > { /** * The automatically-generated internal ID of the user. diff --git a/types/index.d.ts b/types/index.d.ts index 0dc6b83fdf..210c0753ee 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -107,8 +107,9 @@ declare namespace Realm { * ObjectClass * @see { @link https://realm.io/docs/javascript/latest/api/Realm.html#~ObjectClass } */ - interface ObjectClass { - schema: ObjectSchema; + type ObjectClass = any> = { + new(...args: any): Realm.Object; + schema?: ObjectSchema; } type PrimaryKey = number | string | Realm.BSON.ObjectId | Realm.BSON.UUID; @@ -278,7 +279,12 @@ declare namespace Realm { * Object * @see { @link https://realm.io/docs/javascript/latest/api/Realm.Object.html } */ - abstract class Object { + abstract class Object { + /** + * Creates a new object in the database. + */ + constructor(realm: Realm, values: Unmanaged); + /** * @returns An array of the names of the object's properties. */ @@ -319,9 +325,9 @@ declare namespace Realm { /** * @returns void */ - addListener(callback: ObjectChangeCallback): void; + addListener(callback: ObjectChangeCallback): void; - removeListener(callback: ObjectChangeCallback): void; + removeListener(callback: ObjectChangeCallback): void; removeAllListeners(): void; @@ -992,10 +998,10 @@ type ExtractPropertyNamesOfType = { }[keyof T]; /** - * Exchanges properties defined as Realm.List with an optional Array>. + * Exchanges properties defined as Realm.List with an optional Array>. */ type RealmListsRemappedModelPart = { - [K in ExtractPropertyNamesOfType>]?: T[K] extends Realm.List ? Array> : never + [K in ExtractPropertyNamesOfType>]?: T[K] extends Realm.List ? Array> : never } /** @@ -1022,7 +1028,7 @@ type RemappedRealmTypes = * Joins T stripped of all keys which value extends Realm.Collection and all inherited from Realm.Object, * with only the keys which value extends Realm.List, remapped as Arrays. */ -type RealmInsertionModel = OmittedRealmTypes & RemappedRealmTypes; +type Unmanaged = OmittedRealmTypes & RemappedRealmTypes; declare class Realm { static defaultPath: string; @@ -1127,8 +1133,8 @@ declare class Realm { * @param {Realm.UpdateMode} mode? If not provided, `Realm.UpdateMode.Never` is used. * @returns T & Realm.Object */ - create(type: string, properties: RealmInsertionModel, mode?: Realm.UpdateMode.Never): T & Realm.Object; - create(type: string, properties: Partial | Partial>, mode: Realm.UpdateMode.All | Realm.UpdateMode.Modified): T & Realm.Object; + create(type: string, properties: Unmanaged, mode?: Realm.UpdateMode.Never): T & Realm.Object; + create(type: string, properties: Partial | Partial>, mode: Realm.UpdateMode.All | Realm.UpdateMode.Modified): T & Realm.Object; /** * @param {Class} type @@ -1136,8 +1142,8 @@ declare class Realm { * @param {Realm.UpdateMode} mode? If not provided, `Realm.UpdateMode.Never` is used. * @returns T */ - create(type: {new(...arg: any[]): T; }, properties: RealmInsertionModel, mode?: Realm.UpdateMode.Never): T; - create(type: {new(...arg: any[]): T; }, properties: Partial | Partial>, mode: Realm.UpdateMode.All | Realm.UpdateMode.Modified): T; + create(type: {new(...arg: any[]): T; }, properties: Unmanaged, mode?: Realm.UpdateMode.Never): T; + create(type: {new(...arg: any[]): T; }, properties: Partial | Partial>, mode: Realm.UpdateMode.All | Realm.UpdateMode.Modified): T; /** * @param {Realm.Object|Realm.Object[]|Realm.List|Realm.Results|any} object @@ -1170,7 +1176,7 @@ declare class Realm { objectForPrimaryKey(type: {new(...arg: any[]): T; }, key: Realm.PrimaryKey): T | undefined; // Combined definitions - objectForPrimaryKey(type: string | {new(...arg: any[]): T; }, key: Realm.PrimaryKey): (T & Realm.Object) | undefined; + objectForPrimaryKey(type: string | {new(...arg: any[]): T; }, key: Realm.PrimaryKey): (T & Realm.Object) | undefined; /** * @param {string} type diff --git a/vendor/GCDWebServer b/vendor/GCDWebServer deleted file mode 160000 index c98941121a..0000000000 --- a/vendor/GCDWebServer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c98941121a4b96a4fa4ad785790a4a3e119227a5