diff --git a/src/LiveQueryClient.ts b/src/LiveQueryClient.ts index 44f1c552f..4ce6008e7 100644 --- a/src/LiveQueryClient.ts +++ b/src/LiveQueryClient.ts @@ -21,6 +21,7 @@ const OP_TYPES = { SUBSCRIBE: 'subscribe', UNSUBSCRIBE: 'unsubscribe', ERROR: 'error', + QUERY: 'query', }; // The event we get back from LiveQuery server @@ -34,6 +35,7 @@ const OP_EVENTS = { ENTER: 'enter', LEAVE: 'leave', DELETE: 'delete', + RESULT: 'result', }; // The event the LiveQuery client should emit @@ -53,6 +55,7 @@ const SUBSCRIPTION_EMMITER_TYPES = { ENTER: 'enter', LEAVE: 'leave', DELETE: 'delete', + RESULT: 'result', }; // Exponentially-growing random delay @@ -212,7 +215,7 @@ class LiveQueryClient { subscribeRequest.sessionToken = sessionToken; } - const subscription = new LiveQuerySubscription(this.requestId, query, sessionToken); + const subscription = new LiveQuerySubscription(this.requestId, query, sessionToken, this); this.subscriptions.set(this.requestId, subscription); this.requestId += 1; this.connectPromise @@ -425,6 +428,13 @@ class LiveQueryClient { } break; } + case OP_EVENTS.RESULT: { + if (subscription) { + const objects = data.results.map(json => ParseObject.fromJSON(json, false)); + subscription.emit(SUBSCRIPTION_EMMITER_TYPES.RESULT, objects); + } + break; + } default: { // create, update, enter, leave, delete cases if (!subscription) { diff --git a/src/LiveQuerySubscription.ts b/src/LiveQuerySubscription.ts index 79a59d822..0759a5349 100644 --- a/src/LiveQuerySubscription.ts +++ b/src/LiveQuerySubscription.ts @@ -92,6 +92,7 @@ class LiveQuerySubscription { subscribePromise: any; unsubscribePromise: any; subscribed: boolean; + client: any; emitter: EventEmitter; on: EventEmitter['on']; emit: EventEmitter['emit']; @@ -99,11 +100,13 @@ class LiveQuerySubscription { * @param {string | number} id - subscription id * @param {string} query - query to subscribe to * @param {string} sessionToken - optional session token + * @param {object} client - LiveQueryClient instance */ - constructor(id: string | number, query: ParseQuery, sessionToken?: string) { + constructor(id: string | number, query: ParseQuery, sessionToken?: string, client?: any) { this.id = id; this.query = query; this.sessionToken = sessionToken; + this.client = client; this.subscribePromise = resolvingPromise(); this.unsubscribePromise = resolvingPromise(); this.subscribed = false; @@ -130,6 +133,21 @@ class LiveQuerySubscription { return liveQueryClient.unsubscribe(this); }); } + + /** + * Execute a query on this subscription. + * The results will be delivered via the 'result' event. + */ + find() { + if (this.client) { + this.client.connectPromise.then(() => { + this.client.socket.send(JSON.stringify({ + op: 'query', + requestId: this.id, + })); + }); + } + } } export default LiveQuerySubscription; diff --git a/src/__tests__/LiveQueryClient-test.js b/src/__tests__/LiveQueryClient-test.js index c8fa0ba0c..050dcbd83 100644 --- a/src/__tests__/LiveQueryClient-test.js +++ b/src/__tests__/LiveQueryClient-test.js @@ -1,3 +1,4 @@ +jest.dontMock('../LiveQuerySubscription'); jest.dontMock('../LiveQueryClient'); jest.dontMock('../arrayContainsObject'); jest.dontMock('../canBeSerialized'); @@ -23,7 +24,6 @@ jest.dontMock('../UniqueInstanceStateController'); jest.dontMock('../unsavedChildren'); jest.dontMock('../ParseACL'); jest.dontMock('../ParseQuery'); -jest.dontMock('../LiveQuerySubscription'); jest.dontMock('../LocalDatastore'); jest.dontMock('../WebSocketController'); @@ -38,6 +38,7 @@ jest.setMock('../LocalDatastore', mockLocalDatastore); const CoreManager = require('../CoreManager').default; const EventEmitter = require('../EventEmitter').default; const LiveQueryClient = require('../LiveQueryClient').default; +const LiveQuerySubscription = require('../LiveQuerySubscription').default; const ParseObject = require('../ParseObject').default; const ParseQuery = require('../ParseQuery').default; const WebSocketController = require('../WebSocketController').default; @@ -1091,4 +1092,94 @@ describe('LiveQueryClient', () => { const subscription = liveQueryClient.subscribe(); expect(subscription).toBe(undefined); }); + + it('can handle WebSocket result response message', () => { + const liveQueryClient = new LiveQueryClient({ + applicationId: 'applicationId', + serverURL: 'ws://test', + javascriptKey: 'javascriptKey', + masterKey: 'masterKey', + sessionToken: 'sessionToken', + }); + // Add mock subscription + const subscription = new events.EventEmitter(); + liveQueryClient.subscriptions.set(1, subscription); + const object1 = new ParseObject('Test'); + object1.set('key', 'value1'); + const object2 = new ParseObject('Test'); + object2.set('key', 'value2'); + const data = { + op: 'result', + clientId: 1, + requestId: 1, + results: [object1._toFullJSON(), object2._toFullJSON()], + }; + const event = { + data: JSON.stringify(data), + }; + // Register checked in advance + let isChecked = false; + subscription.on('result', function (objects) { + isChecked = true; + expect(objects.length).toBe(2); + expect(objects[0].get('key')).toEqual('value1'); + expect(objects[1].get('key')).toEqual('value2'); + }); + + liveQueryClient._handleWebSocketMessage(event); + + expect(isChecked).toBe(true); + }); + + it('LiveQuerySubscription class has find method', () => { + expect(typeof LiveQuerySubscription.prototype.find).toBe('function'); + }); + + it('subscription has find method', () => { + const liveQueryClient = new LiveQueryClient({ + applicationId: 'applicationId', + serverURL: 'ws://test', + javascriptKey: 'javascriptKey', + masterKey: 'masterKey', + sessionToken: 'sessionToken', + }); + const query = new ParseQuery('Test'); + query.equalTo('key', 'value'); + + const subscription = liveQueryClient.subscribe(query); + + expect(subscription).toBeInstanceOf(LiveQuerySubscription); + expect(typeof subscription.find).toBe('function'); + }); + + it('can send query message via subscription', async () => { + const liveQueryClient = new LiveQueryClient({ + applicationId: 'applicationId', + serverURL: 'ws://test', + javascriptKey: 'javascriptKey', + masterKey: 'masterKey', + sessionToken: 'sessionToken', + }); + liveQueryClient.socket = { + send: jest.fn(), + }; + const query = new ParseQuery('Test'); + query.equalTo('key', 'value'); + + const subscription = liveQueryClient.subscribe(query); + liveQueryClient.connectPromise.resolve(); + await liveQueryClient.connectPromise; + + subscription.find(); + + // Need to wait for the sendMessage promise to resolve + await Promise.resolve(); + + const messageStr = liveQueryClient.socket.send.mock.calls[1][0]; + const message = JSON.parse(messageStr); + expect(message).toEqual({ + op: 'query', + requestId: 1, + }); + }); }); diff --git a/types/LiveQuerySubscription.d.ts b/types/LiveQuerySubscription.d.ts index 7bc4df0e7..e47a78c8d 100644 --- a/types/LiveQuerySubscription.d.ts +++ b/types/LiveQuerySubscription.d.ts @@ -89,15 +89,21 @@ declare class LiveQuerySubscription { subscribePromise: any; unsubscribePromise: any; subscribed: boolean; + client: any; emitter: EventEmitter; on: EventEmitter['on']; emit: EventEmitter['emit']; - constructor(id: string | number, query: ParseQuery, sessionToken?: string); + constructor(id: string | number, query: ParseQuery, sessionToken?: string, client?: any); /** * Close the subscription * * @returns {Promise} */ unsubscribe(): Promise; + /** + * Execute a query on this subscription. + * The results will be delivered via the 'result' event. + */ + find(): void; } export default LiveQuerySubscription;