diff --git a/CHANGELOG.md b/CHANGELOG.md index c802964afd..2e2a2da958 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ * None ### Enhancements -* None +* Experimental feature: The new instance members `App.baseUrl` and `App.updateBaseUrl()` allow for retrieving and updating the base URL currently used for requests sent to Atlas App Services. These APIs are only available after importing `"realm/experimental/base-url"`. ([#6518](/~https://github.com/realm/realm-js/pull/6518)) ### Fixed * ([#????](/~https://github.com/realm/realm-js/issues/????), since v?.?.?) diff --git a/integration-tests/environments/react-native-test-app/ios/Podfile.lock b/integration-tests/environments/react-native-test-app/ios/Podfile.lock index 2e01bab1f0..80de20556e 100644 --- a/integration-tests/environments/react-native-test-app/ios/Podfile.lock +++ b/integration-tests/environments/react-native-test-app/ios/Podfile.lock @@ -1107,7 +1107,7 @@ PODS: - React-Core - React-jsi - ReactTestApp-Resources (1.0.0-dev) - - RealmJS (12.7.0-rc.0): + - RealmJS (12.7.1): - React - RNFS (2.20.0): - React-Core @@ -1356,7 +1356,7 @@ SPEC CHECKSUMS: ReactNativeHost: 446b46cbfc2d16420bfd249e9064802e929fa9b7 ReactTestApp-DevSupport: 5cd1b02b4d146811d29dc2455a249f0743fda504 ReactTestApp-Resources: d200e68756fa45c648f369210bd7ee0c14759f5a - RealmJS: 0c85b5fd6406447f4526d7b73d0a8025cd8b342a + RealmJS: 3fdc3b6cb6d4ff67eaf0e5f7f861be51304e7525 RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 Yoga: 805bf71192903b20fc14babe48080582fee65a80 diff --git a/integration-tests/environments/react-native-test-app/metro.config.js b/integration-tests/environments/react-native-test-app/metro.config.js index 64fa756b2e..3d7c2672a7 100644 --- a/integration-tests/environments/react-native-test-app/metro.config.js +++ b/integration-tests/environments/react-native-test-app/metro.config.js @@ -17,6 +17,7 @@ //////////////////////////////////////////////////////////////////////////// const { makeMetroConfig } = require("@rnx-kit/metro-config"); + module.exports = makeMetroConfig({ transformer: { getTransformOptions: async () => ({ diff --git a/integration-tests/tests/src/hooks/import-app-before.ts b/integration-tests/tests/src/hooks/import-app-before.ts index c3bad5017c..ac9dd0fcb8 100644 --- a/integration-tests/tests/src/hooks/import-app-before.ts +++ b/integration-tests/tests/src/hooks/import-app-before.ts @@ -16,7 +16,7 @@ // //////////////////////////////////////////////////////////////////////////// -import Realm from "realm"; +import Realm, { AppConfiguration } from "realm"; import { AppConfig, AppImporter, Credentials } from "@realm/app-importer"; import { mongodbServiceType } from "../utils/ExtendedAppConfigBuilder"; @@ -39,14 +39,6 @@ export { baseUrl }; const allowSkippingServerTests = typeof environment.baseUrl === "undefined" && missingServer !== false; -export type AppConfigurationRelaxed = { - id?: string; - baseUrl?: string; - timeout?: number; - multiplexSessions?: boolean; - baseFilePath?: string; -}; - const credentials: Credentials = typeof publicKey === "string" && typeof privateKey === "string" ? { @@ -94,7 +86,10 @@ function ensureSkippedAppImportAfterHook() { * If the `missingServer` context is set the suite will be skipped. * If the import fails due to a connection refusal, the suite will be skipped and a warning printed at the end of the test run. */ -export function importAppBefore(config: AppConfig | { config: AppConfig }, sdkConfig?: AppConfigurationRelaxed): void { +export function importAppBefore( + config: AppConfig | { config: AppConfig }, + sdkConfig?: Partial, +): void { // Unwrap when passed a builder directly if ("config" in config) { return importAppBefore(config.config, sdkConfig); diff --git a/integration-tests/tests/src/tests.ts b/integration-tests/tests/src/tests.ts index 7a7096dcc5..cbddde1842 100644 --- a/integration-tests/tests/src/tests.ts +++ b/integration-tests/tests/src/tests.ts @@ -22,6 +22,8 @@ import "./tests/credentials/email-password"; import "./tests/credentials/function"; import "./tests/credentials/jwt"; +import "./tests/experimental/base-url"; + import "./tests/sync/app"; import "./tests/sync/asymmetric"; import "./tests/sync/client-reset"; diff --git a/integration-tests/tests/src/tests/experimental/base-url.ts b/integration-tests/tests/src/tests/experimental/base-url.ts new file mode 100644 index 0000000000..9101ebcc0a --- /dev/null +++ b/integration-tests/tests/src/tests/experimental/base-url.ts @@ -0,0 +1,55 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 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/experimental/base-url"; + +import { baseUrl as originalBaseUrl, importAppBefore } from "../../hooks"; +import { buildAppConfig } from "../../utils/build-app-config"; + +describe.skipIf(environment.missingServer, "Base URL", () => { + importAppBefore(buildAppConfig("with-anon").anonAuth()); + + it("returns the base URL used", function (this: AppContext) { + expect(this.app.baseUrl).equals(originalBaseUrl); + }); + + // TODO: Should implement when I've got a working fetch mock. + it.skip("updates the URL", async function (this: AppContext) { + // TODO + }); + + it.skip("resets to default URL", async function (this: AppContext) { + // TODO + }); + + it("throws when assigning via setter", function (this: AppContext) { + // @ts-expect-error Assigning to read-only property. + expect(() => (this.app.baseUrl = "new URL")).to.throw("Cannot assign the base URL, please use 'updateBaseUrl()'"); + + expect(this.app.baseUrl).equals(originalBaseUrl); + }); + + it("rejects when updating to invalid URL", async function (this: AppContext) { + await expect(this.app.updateBaseUrl("https://invalid")).to.be.rejectedWith( + "request to https://invalid/api/client/", + ); + + expect(this.app.baseUrl).equals(originalBaseUrl); + }); +}); diff --git a/integration-tests/tests/tsconfig.common.json b/integration-tests/tests/tsconfig.common.json index 92f284a8bb..00cf6ef486 100644 --- a/integration-tests/tests/tsconfig.common.json +++ b/integration-tests/tests/tsconfig.common.json @@ -3,7 +3,7 @@ "composite": true, "target": "es2022", "module": "es2022", - "moduleResolution": "node", + "moduleResolution": "Bundler", "useDefineForClassFields": false, "strict": true, "strictFunctionTypes": false, diff --git a/integration-tests/tests/tsconfig.json b/integration-tests/tests/tsconfig.json index 6c132bea4a..1ea283bb59 100644 --- a/integration-tests/tests/tsconfig.json +++ b/integration-tests/tests/tsconfig.json @@ -4,4 +4,4 @@ { "path": "./tsconfig.common.json" }, { "path": "./tsconfig.node.json" } ] -} \ No newline at end of file +} diff --git a/packages/realm/bindgen/js_opt_in_spec.yml b/packages/realm/bindgen/js_opt_in_spec.yml index 52e80159fa..d1489c43e7 100644 --- a/packages/realm/bindgen/js_opt_in_spec.yml +++ b/packages/realm/bindgen/js_opt_in_spec.yml @@ -485,6 +485,8 @@ classes: - unsubscribe - call_function - make_streaming_request + - update_base_url + - get_base_url WatchStream: methods: diff --git a/packages/realm/experimental/base-url.js b/packages/realm/experimental/base-url.js new file mode 100644 index 0000000000..ad91eba309 --- /dev/null +++ b/packages/realm/experimental/base-url.js @@ -0,0 +1,29 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 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. +// +//////////////////////////////////////////////////////////////////////////// + +// Our use of `exports` in `packages/realm/package.json` is not enabled by +// default when using Metro and RN. In these cases, modules imported from +// "realm/experimental" will search for the file in the same path, rather +// than what is pointed to under `exports`. Thus, we use this .js file to +// in turn import the necessary module. + +// (Enabling `unstable_enablePackageExports` in the metro config unexpectedly +// does not work.) + +/* eslint-env commonjs */ +module.exports = require("../dist/experimental/base-url"); diff --git a/packages/realm/package.json b/packages/realm/package.json index fa5bb81cf2..d7bc878db3 100644 --- a/packages/realm/package.json +++ b/packages/realm/package.json @@ -42,6 +42,10 @@ "node": "./dist/platform/node/index.js", "react-native": "./index.react-native.js" }, + "./experimental/base-url": { + "types": "./dist/public-types/experimental/base-url.d.ts", + "default": "./dist/experimental/base-url.js" + }, "./scripts/submit-analytics": "./scripts/submit-analytics.mjs", "./react-native.config.js": "./react-native.config.js", "./package.json": "./package.json" diff --git a/packages/realm/src/experimental/base-url.ts b/packages/realm/src/experimental/base-url.ts new file mode 100644 index 0000000000..1a243a303b --- /dev/null +++ b/packages/realm/src/experimental/base-url.ts @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2024 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 { App } from "../app-services/App"; + +declare module "../app-services/App" { + interface App { + /** + * Get the current base URL used for sending requests to Atlas App Services. + * + * If an {@link App.updateBaseUrl | updateBaseUrl} operation is currently in + * progress, this value will not be updated with the new value until that + * operation has completed. + * @experimental This feature is experimental and may be changed or removed. + */ + get baseUrl(): string; + + /** + * Update the base URL used for sending requests to Atlas App Services. If this is + * set to an empty string or `null`, it will reset the base URL to the default one. + * + * If this operation fails, the app will continue to use the original base URL. + * If another {@link App} operation is started while this function is in progress, + * that request will use the original base URL location information. + * @experimental This feature is experimental and may be changed or removed. + */ + updateBaseUrl(newUrl: string | null): Promise; + } +} + +Object.defineProperty(App.prototype, "baseUrl", { + get(this: App) { + return this.internal.getBaseUrl(); + }, + set() { + throw new Error("Cannot assign the base URL, please use 'updateBaseUrl()'."); + }, +}); + +App.prototype.updateBaseUrl = function (this: App, newUrl: string | null) { + return this.internal.updateBaseUrl(newUrl ?? undefined); +};