diff --git a/package.json b/package.json index 90ce473b0d..6cf9285199 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,6 @@ "@graphql-tools/schema": "10.0.31", "@graphql-tools/utils": "11.0.0", "@parse/fs-files-adapter": "3.0.0", - "@parse/push-adapter": "8.4.0", "bcryptjs": "3.0.3", "commander": "14.0.3", "cors": "2.8.6", @@ -146,7 +145,8 @@ "parse-server": "bin/parse-server" }, "optionalDependencies": { - "@node-rs/bcrypt": "1.10.7" + "@node-rs/bcrypt": "1.10.7", + "@parse/push-adapter": "8.4.0" }, "collective": { "type": "opencollective", diff --git a/spec/index.spec.js b/spec/index.spec.js index 988f35cc3e..d143d39fb1 100644 --- a/spec/index.spec.js +++ b/spec/index.spec.js @@ -4,6 +4,7 @@ const parseServerPackage = require('../package.json'); const MockEmailAdapterWithOptions = require('./support/MockEmailAdapterWithOptions'); const ParseServer = require('../lib/index'); const Config = require('../lib/Config'); +const adapterLoader = require('../lib/Adapters/AdapterLoader'); const express = require('express'); const MongoStorageAdapter = require('../lib/Adapters/Storage/Mongo/MongoStorageAdapter').default; @@ -211,6 +212,52 @@ describe('server', () => { .catch(done.fail); }); + it('can start when push is not configured and optional push adapter is missing', async () => { + const originalLoadModule = adapterLoader.loadModule; + const loadModuleSpy = spyOn(adapterLoader, 'loadModule').and.callFake(modulePath => { + if (modulePath === '@parse/push-adapter') { + const error = new Error("Cannot find package '@parse/push-adapter'"); + error.code = 'ERR_MODULE_NOT_FOUND'; + return Promise.reject(error); + } + return originalLoadModule(modulePath); + }); + + try { + await reconfigureServer({ + push: undefined, + }); + const config = Config.get('test'); + expect(config.hasPushSupport).toEqual(false); + } finally { + loadModuleSpy.and.callThrough(); + } + }); + + it('throws clear error when push is configured and optional push adapter is missing', async () => { + const originalLoadModule = adapterLoader.loadModule; + const loadModuleSpy = spyOn(adapterLoader, 'loadModule').and.callFake(modulePath => { + if (modulePath === '@parse/push-adapter') { + const error = new Error("Cannot find package '@parse/push-adapter'"); + error.code = 'ERR_MODULE_NOT_FOUND'; + return Promise.reject(error); + } + return originalLoadModule(modulePath); + }); + + try { + await expectAsync( + reconfigureServer({ + push: {}, + }) + ).toBeRejectedWithError( + 'Push is configured but the optional dependency "@parse/push-adapter" is not installed. Install "@parse/push-adapter" or configure "push.adapter".' + ); + } finally { + loadModuleSpy.and.callThrough(); + } + }); + it('can properly sets the push support ', done => { reconfigureServer({ push: { diff --git a/src/Controllers/index.js b/src/Controllers/index.js index 9397dac561..4581f126ff 100644 --- a/src/Controllers/index.js +++ b/src/Controllers/index.js @@ -169,6 +169,13 @@ interface PushControlling { pushWorker: PushWorker; } +function isPushAdapterModuleMissing(error: any): boolean { + const message = `${error?.message || error || ''}`; + const hasMissingCode = + error?.code === 'ERR_MODULE_NOT_FOUND' || error?.code === 'MODULE_NOT_FOUND'; + return hasMissingCode && message.includes('@parse/push-adapter'); +} + export async function getPushController(options: ParseServerOptions): PushControlling { const { scheduledPush, push } = options; @@ -179,7 +186,19 @@ export async function getPushController(options: ParseServerOptions): PushContro } // Pass the push options too as it works with the default - const ParsePushAdapter = await loadModule('@parse/push-adapter'); + let ParsePushAdapter; + try { + ParsePushAdapter = await loadModule('@parse/push-adapter'); + } catch (error) { + if (!isPushAdapterModuleMissing(error)) { + throw error; + } + if (push && !pushOptions.adapter) { + throw new Error( + 'Push is configured but the optional dependency "@parse/push-adapter" is not installed. Install "@parse/push-adapter" or configure "push.adapter".' + ); + } + } const pushAdapter = loadAdapter( pushOptions && pushOptions.adapter, ParsePushAdapter, @@ -195,7 +214,7 @@ export async function getPushController(options: ParseServerOptions): PushContro const pushControllerQueue = new PushQueue(pushQueueOptions); let pushWorker; - if (!disablePushWorker) { + if (!disablePushWorker && hasPushSupport) { pushWorker = new PushWorker(pushAdapter, pushQueueOptions); } return {