diff --git a/spec/ParseLiveQuery.spec.js b/spec/ParseLiveQuery.spec.js index 015725ac46..58c9610d77 100644 --- a/spec/ParseLiveQuery.spec.js +++ b/spec/ParseLiveQuery.spec.js @@ -800,6 +800,45 @@ describe('ParseLiveQuery', function () { await object.save(); }); + it('can handle select beforeUnsubscribe trigger', async done => { + await reconfigureServer({ + liveQuery: { + classNames: ['TestObject'], + }, + startLiveQueryServer: true, + verbose: false, + silent: true, + }); + const user = new Parse.User(); + user.setUsername('username'); + user.setPassword('password'); + await user.signUp(); + + Parse.Cloud.beforeSubscribe(TestObject, req => { + expect(req.requestId).toBe(1); + expect(req.user).toBeDefined(); + }); + + Parse.Cloud.beforeUnsubscribe(TestObject, req => { + expect(req.requestId).toBe(1); + expect(req.user).toBeDefined(); + expect(req.user.get('username')).toBe('username'); + done(); + }); + + const object = new TestObject(); + await object.save(); + + const query = new Parse.Query(TestObject); + query.equalTo('objectId', object.id); + const subscription = await query.subscribe(); + + object.set({ foo: 'bar', yolo: 'abc' }); + await object.save(); + + await subscription.unsubscribe(); + }); + it('LiveQuery with ACL', async () => { await reconfigureServer({ liveQuery: { diff --git a/src/LiveQuery/ParseLiveQueryServer.js b/src/LiveQuery/ParseLiveQueryServer.js index d0b535f3a1..933fd41372 100644 --- a/src/LiveQuery/ParseLiveQueryServer.js +++ b/src/LiveQuery/ParseLiveQueryServer.js @@ -968,7 +968,7 @@ class ParseLiveQueryServer { this._handleSubscribe(parseWebsocket, request); } - _handleUnsubscribe(parseWebsocket: any, request: any, notifyClient: boolean = true): any { + async _handleUnsubscribe(parseWebsocket: any, request: any, notifyClient: boolean = true): any { // If we can not find this client, return error to client if (!Object.prototype.hasOwnProperty.call(parseWebsocket, 'clientId')) { Client.pushError( @@ -1015,11 +1015,33 @@ class ParseLiveQueryServer { return; } + const subscription = subscriptionInfo.subscription; + const className = subscription.className; + const trigger = getTrigger(className, 'beforeUnsubscribe', Parse.applicationId); + if (trigger) { + const auth = await this.getAuthFromClient(client, request.requestId, request.sessionToken); + if (auth && auth.user) { + request.user = auth.user; + } + + request.sessionToken = subscriptionInfo.sessionToken; + request.useMasterKey = client.hasMasterKey; + request.installationId = client.installationId; + + try { + await runTrigger(trigger, `beforeUnsubscribe.${className}`, request, auth); + } catch (error) { + Client.pushError(parseWebsocket, error.code || Parse.Error.SCRIPT_FAILED, error.message || error, false); + logger.error( + `Failed running beforeUnsubscribe for session ${request.sessionToken} with:\n Error: ` + + JSON.stringify(error) + ); + } + } + // Remove subscription from client client.deleteSubscriptionInfo(requestId); // Remove client from subscription - const subscription = subscriptionInfo.subscription; - const className = subscription.className; subscription.deleteClientSubscription(parseWebsocket.clientId, requestId); // If there is no client which is subscribing this subscription, remove it from subscriptions const classSubscriptions = this.subscriptions.get(className); diff --git a/src/cloud-code/Parse.Cloud.js b/src/cloud-code/Parse.Cloud.js index 01bf65f42c..8c335e9f4e 100644 --- a/src/cloud-code/Parse.Cloud.js +++ b/src/cloud-code/Parse.Cloud.js @@ -729,6 +729,42 @@ ParseCloud.onLiveQueryEvent = function (handler) { triggers.addLiveQueryEventHandler(handler, Parse.applicationId); }; +/** + * Registers a before live query subscription function. + * + * **Available in Cloud Code only.** + * + * If you want to use beforeUnsubscribe for a predefined class in the Parse JavaScript SDK (e.g. {@link Parse.User}), you should pass the class itself and not the String for arg1. + * ``` + * Parse.Cloud.beforeUnsubscribe('MyCustomClass', (request) => { + * // code here + * }, (request) => { + * // validation code here + * }); + * + * Parse.Cloud.beforeUnsubscribe(Parse.User, (request) => { + * // code here + * }, { ...validationObject }); + *``` + * + * @method beforeUnsubscribe + * @name Parse.Cloud.beforeUnsubscribe + * @param {(String|Parse.Object)} arg1 The Parse.Object subclass to register the before subscription function for. This can instead be a String that is the className of the subclass. + * @param {Function} func The function to run before a subscription. This function can be async and should take one parameter, a {@link Parse.Cloud.TriggerRequest}. + * @param {(Object|Function)} validator An optional function to help validating cloud code. This function can be an async function and should take one parameter a {@link Parse.Cloud.TriggerRequest}, or a {@link Parse.Cloud.ValidatorObject}. + */ +ParseCloud.beforeUnsubscribe = function (parseClass, handler, validationHandler) { + validateValidator(validationHandler); + const className = triggers.getClassName(parseClass); + triggers.addTrigger( + triggers.Types.beforeUnsubscribe, + className, + handler, + Parse.applicationId, + validationHandler + ); +}; + /** * Registers an after live query server event function. * diff --git a/src/triggers.js b/src/triggers.js index 5c4755af54..9698c7cd78 100644 --- a/src/triggers.js +++ b/src/triggers.js @@ -14,6 +14,7 @@ export const Types = { afterFind: 'afterFind', beforeConnect: 'beforeConnect', beforeSubscribe: 'beforeSubscribe', + beforeUnsubscribe: 'beforeUnsubscribe', afterEvent: 'afterEvent', };