@@ -3,7 +3,13 @@ import logger from '../logger';
|
||||
import type { FlattenedObjectData } from './Subscription';
|
||||
export type Message = { [attr: string]: any };
|
||||
|
||||
const dafaultFields = ['className', 'objectId', 'updatedAt', 'createdAt', 'ACL'];
|
||||
const dafaultFields = [
|
||||
'className',
|
||||
'objectId',
|
||||
'updatedAt',
|
||||
'createdAt',
|
||||
'ACL',
|
||||
];
|
||||
|
||||
class Client {
|
||||
id: number;
|
||||
@@ -42,13 +48,21 @@ class Client {
|
||||
parseWebSocket.send(message);
|
||||
}
|
||||
|
||||
static pushError(parseWebSocket: any, code: number, error: string, reconnect: boolean = true): void {
|
||||
Client.pushResponse(parseWebSocket, JSON.stringify({
|
||||
'op': 'error',
|
||||
'error': error,
|
||||
'code': code,
|
||||
'reconnect': reconnect
|
||||
}));
|
||||
static pushError(
|
||||
parseWebSocket: any,
|
||||
code: number,
|
||||
error: string,
|
||||
reconnect: boolean = true
|
||||
): void {
|
||||
Client.pushResponse(
|
||||
parseWebSocket,
|
||||
JSON.stringify({
|
||||
op: 'error',
|
||||
error: error,
|
||||
code: code,
|
||||
reconnect: reconnect,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
addSubscriptionInfo(requestId: number, subscriptionInfo: any): void {
|
||||
@@ -66,8 +80,8 @@ class Client {
|
||||
_pushEvent(type: string): Function {
|
||||
return function(subscriptionId: number, parseObjectJSON: any): void {
|
||||
const response: Message = {
|
||||
'op' : type,
|
||||
'clientId' : this.id
|
||||
op: type,
|
||||
clientId: this.id,
|
||||
};
|
||||
if (typeof subscriptionId !== 'undefined') {
|
||||
response['requestId'] = subscriptionId;
|
||||
@@ -80,7 +94,7 @@ class Client {
|
||||
response['object'] = this._toJSONWithFields(parseObjectJSON, fields);
|
||||
}
|
||||
Client.pushResponse(this.parseWebSocket, JSON.stringify(response));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
_toJSONWithFields(parseObjectJSON: any, fields: any): FlattenedObjectData {
|
||||
@@ -100,6 +114,4 @@ class Client {
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
Client
|
||||
}
|
||||
export { Client };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ParsePubSub } from './ParsePubSub';
|
||||
import Parse from 'parse/node';
|
||||
import Parse from 'parse/node';
|
||||
import logger from '../logger';
|
||||
|
||||
class ParseCloudCodePublisher {
|
||||
@@ -21,11 +21,15 @@ class ParseCloudCodePublisher {
|
||||
|
||||
// Request is the request object from cloud code functions. request.object is a ParseObject.
|
||||
_onCloudCodeMessage(type: string, request: any): void {
|
||||
logger.verbose('Raw request from cloud code current : %j | original : %j', request.object, request.original);
|
||||
logger.verbose(
|
||||
'Raw request from cloud code current : %j | original : %j',
|
||||
request.object,
|
||||
request.original
|
||||
);
|
||||
// We need the full JSON which includes className
|
||||
const message = {
|
||||
currentParseObject: request.object._toFullJSON()
|
||||
}
|
||||
currentParseObject: request.object._toFullJSON(),
|
||||
};
|
||||
if (request.original) {
|
||||
message.originalParseObject = request.original._toFullJSON();
|
||||
}
|
||||
@@ -33,6 +37,4 @@ class ParseCloudCodePublisher {
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
ParseCloudCodePublisher
|
||||
}
|
||||
export { ParseCloudCodePublisher };
|
||||
|
||||
@@ -17,7 +17,7 @@ class ParseLiveQueryServer {
|
||||
// className -> (queryHash -> subscription)
|
||||
subscriptions: Object;
|
||||
parseWebSocketServer: Object;
|
||||
keyPairs : any;
|
||||
keyPairs: any;
|
||||
// The subscriber we use to get object update from publisher
|
||||
subscriber: Object;
|
||||
|
||||
@@ -49,7 +49,7 @@ class ParseLiveQueryServer {
|
||||
// Initialize websocket server
|
||||
this.parseWebSocketServer = new ParseWebSocketServer(
|
||||
server,
|
||||
(parseWebsocket) => this._onConnect(parseWebsocket),
|
||||
parseWebsocket => this._onConnect(parseWebsocket),
|
||||
config.websocketTimeout
|
||||
);
|
||||
|
||||
@@ -64,7 +64,7 @@ class ParseLiveQueryServer {
|
||||
let message;
|
||||
try {
|
||||
message = JSON.parse(messageStr);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
logger.error('unable to parse message', messageStr, e);
|
||||
return;
|
||||
}
|
||||
@@ -74,7 +74,11 @@ class ParseLiveQueryServer {
|
||||
} else if (channel === Parse.applicationId + 'afterDelete') {
|
||||
this._onAfterDelete(message);
|
||||
} else {
|
||||
logger.error('Get message %s from unknown channel %j', message, channel);
|
||||
logger.error(
|
||||
'Get message %s from unknown channel %j',
|
||||
message,
|
||||
channel
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -108,7 +112,11 @@ class ParseLiveQueryServer {
|
||||
|
||||
const deletedParseObject = message.currentParseObject.toJSON();
|
||||
const className = deletedParseObject.className;
|
||||
logger.verbose('ClassName: %j | ObjectId: %s', className, deletedParseObject.id);
|
||||
logger.verbose(
|
||||
'ClassName: %j | ObjectId: %s',
|
||||
className,
|
||||
deletedParseObject.id
|
||||
);
|
||||
logger.verbose('Current client number : %d', this.clients.size);
|
||||
|
||||
const classSubscriptions = this.subscriptions.get(className);
|
||||
@@ -117,11 +125,16 @@ class ParseLiveQueryServer {
|
||||
return;
|
||||
}
|
||||
for (const subscription of classSubscriptions.values()) {
|
||||
const isSubscriptionMatched = this._matchesSubscription(deletedParseObject, subscription);
|
||||
const isSubscriptionMatched = this._matchesSubscription(
|
||||
deletedParseObject,
|
||||
subscription
|
||||
);
|
||||
if (!isSubscriptionMatched) {
|
||||
continue;
|
||||
}
|
||||
for (const [clientId, requestIds] of _.entries(subscription.clientRequestIds)) {
|
||||
for (const [clientId, requestIds] of _.entries(
|
||||
subscription.clientRequestIds
|
||||
)) {
|
||||
const client = this.clients.get(clientId);
|
||||
if (typeof client === 'undefined') {
|
||||
continue;
|
||||
@@ -129,14 +142,17 @@ class ParseLiveQueryServer {
|
||||
for (const requestId of requestIds) {
|
||||
const acl = message.currentParseObject.getACL();
|
||||
// Check ACL
|
||||
this._matchesACL(acl, client, requestId).then((isMatched) => {
|
||||
if (!isMatched) {
|
||||
return null;
|
||||
this._matchesACL(acl, client, requestId).then(
|
||||
isMatched => {
|
||||
if (!isMatched) {
|
||||
return null;
|
||||
}
|
||||
client.pushDelete(requestId, deletedParseObject);
|
||||
},
|
||||
error => {
|
||||
logger.error('Matching ACL error : ', error);
|
||||
}
|
||||
client.pushDelete(requestId, deletedParseObject);
|
||||
}, (error) => {
|
||||
logger.error('Matching ACL error : ', error);
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +169,11 @@ class ParseLiveQueryServer {
|
||||
}
|
||||
const currentParseObject = message.currentParseObject.toJSON();
|
||||
const className = currentParseObject.className;
|
||||
logger.verbose('ClassName: %s | ObjectId: %s', className, currentParseObject.id);
|
||||
logger.verbose(
|
||||
'ClassName: %s | ObjectId: %s',
|
||||
className,
|
||||
currentParseObject.id
|
||||
);
|
||||
logger.verbose('Current client number : %d', this.clients.size);
|
||||
|
||||
const classSubscriptions = this.subscriptions.get(className);
|
||||
@@ -162,9 +182,17 @@ class ParseLiveQueryServer {
|
||||
return;
|
||||
}
|
||||
for (const subscription of classSubscriptions.values()) {
|
||||
const isOriginalSubscriptionMatched = this._matchesSubscription(originalParseObject, subscription);
|
||||
const isCurrentSubscriptionMatched = this._matchesSubscription(currentParseObject, subscription);
|
||||
for (const [clientId, requestIds] of _.entries(subscription.clientRequestIds)) {
|
||||
const isOriginalSubscriptionMatched = this._matchesSubscription(
|
||||
originalParseObject,
|
||||
subscription
|
||||
);
|
||||
const isCurrentSubscriptionMatched = this._matchesSubscription(
|
||||
currentParseObject,
|
||||
subscription
|
||||
);
|
||||
for (const [clientId, requestIds] of _.entries(
|
||||
subscription.clientRequestIds
|
||||
)) {
|
||||
const client = this.clients.get(clientId);
|
||||
if (typeof client === 'undefined') {
|
||||
continue;
|
||||
@@ -180,7 +208,11 @@ class ParseLiveQueryServer {
|
||||
if (message.originalParseObject) {
|
||||
originalACL = message.originalParseObject.getACL();
|
||||
}
|
||||
originalACLCheckingPromise = this._matchesACL(originalACL, client, requestId);
|
||||
originalACLCheckingPromise = this._matchesACL(
|
||||
originalACL,
|
||||
client,
|
||||
requestId
|
||||
);
|
||||
}
|
||||
// Set current ParseObject ACL checking promise, if the object does not match
|
||||
// subscription, we do not need to check ACL
|
||||
@@ -189,56 +221,62 @@ class ParseLiveQueryServer {
|
||||
currentACLCheckingPromise = Promise.resolve(false);
|
||||
} else {
|
||||
const currentACL = message.currentParseObject.getACL();
|
||||
currentACLCheckingPromise = this._matchesACL(currentACL, client, requestId);
|
||||
currentACLCheckingPromise = this._matchesACL(
|
||||
currentACL,
|
||||
client,
|
||||
requestId
|
||||
);
|
||||
}
|
||||
|
||||
Promise.all(
|
||||
[
|
||||
originalACLCheckingPromise,
|
||||
currentACLCheckingPromise
|
||||
]
|
||||
).then(([isOriginalMatched, isCurrentMatched]) => {
|
||||
logger.verbose('Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s',
|
||||
originalParseObject,
|
||||
currentParseObject,
|
||||
isOriginalSubscriptionMatched,
|
||||
isCurrentSubscriptionMatched,
|
||||
isOriginalMatched,
|
||||
isCurrentMatched,
|
||||
subscription.hash
|
||||
);
|
||||
Promise.all([
|
||||
originalACLCheckingPromise,
|
||||
currentACLCheckingPromise,
|
||||
]).then(
|
||||
([isOriginalMatched, isCurrentMatched]) => {
|
||||
logger.verbose(
|
||||
'Original %j | Current %j | Match: %s, %s, %s, %s | Query: %s',
|
||||
originalParseObject,
|
||||
currentParseObject,
|
||||
isOriginalSubscriptionMatched,
|
||||
isCurrentSubscriptionMatched,
|
||||
isOriginalMatched,
|
||||
isCurrentMatched,
|
||||
subscription.hash
|
||||
);
|
||||
|
||||
// Decide event type
|
||||
let type;
|
||||
if (isOriginalMatched && isCurrentMatched) {
|
||||
type = 'Update';
|
||||
} else if (isOriginalMatched && !isCurrentMatched) {
|
||||
type = 'Leave';
|
||||
} else if (!isOriginalMatched && isCurrentMatched) {
|
||||
if (originalParseObject) {
|
||||
type = 'Enter';
|
||||
// Decide event type
|
||||
let type;
|
||||
if (isOriginalMatched && isCurrentMatched) {
|
||||
type = 'Update';
|
||||
} else if (isOriginalMatched && !isCurrentMatched) {
|
||||
type = 'Leave';
|
||||
} else if (!isOriginalMatched && isCurrentMatched) {
|
||||
if (originalParseObject) {
|
||||
type = 'Enter';
|
||||
} else {
|
||||
type = 'Create';
|
||||
}
|
||||
} else {
|
||||
type = 'Create';
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
const functionName = 'push' + type;
|
||||
client[functionName](requestId, currentParseObject);
|
||||
},
|
||||
error => {
|
||||
logger.error('Matching ACL error : ', error);
|
||||
}
|
||||
const functionName = 'push' + type;
|
||||
client[functionName](requestId, currentParseObject);
|
||||
}, (error) => {
|
||||
logger.error('Matching ACL error : ', error);
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onConnect(parseWebsocket: any): void {
|
||||
parseWebsocket.on('message', (request) => {
|
||||
parseWebsocket.on('message', request => {
|
||||
if (typeof request === 'string') {
|
||||
try {
|
||||
request = JSON.parse(request);
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
logger.error('unable to parse request', request, e);
|
||||
return;
|
||||
}
|
||||
@@ -246,28 +284,31 @@ class ParseLiveQueryServer {
|
||||
logger.verbose('Request: %j', request);
|
||||
|
||||
// Check whether this request is a valid request, return error directly if not
|
||||
if (!tv4.validate(request, RequestSchema['general']) || !tv4.validate(request, RequestSchema[request.op])) {
|
||||
if (
|
||||
!tv4.validate(request, RequestSchema['general']) ||
|
||||
!tv4.validate(request, RequestSchema[request.op])
|
||||
) {
|
||||
Client.pushError(parseWebsocket, 1, tv4.error.message);
|
||||
logger.error('Connect message error %s', tv4.error.message);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(request.op) {
|
||||
case 'connect':
|
||||
this._handleConnect(parseWebsocket, request);
|
||||
break;
|
||||
case 'subscribe':
|
||||
this._handleSubscribe(parseWebsocket, request);
|
||||
break;
|
||||
case 'update':
|
||||
this._handleUpdateSubscription(parseWebsocket, request);
|
||||
break;
|
||||
case 'unsubscribe':
|
||||
this._handleUnsubscribe(parseWebsocket, request);
|
||||
break;
|
||||
default:
|
||||
Client.pushError(parseWebsocket, 3, 'Get unknown operation');
|
||||
logger.error('Get unknown operation', request.op);
|
||||
switch (request.op) {
|
||||
case 'connect':
|
||||
this._handleConnect(parseWebsocket, request);
|
||||
break;
|
||||
case 'subscribe':
|
||||
this._handleSubscribe(parseWebsocket, request);
|
||||
break;
|
||||
case 'update':
|
||||
this._handleUpdateSubscription(parseWebsocket, request);
|
||||
break;
|
||||
case 'unsubscribe':
|
||||
this._handleUnsubscribe(parseWebsocket, request);
|
||||
break;
|
||||
default:
|
||||
Client.pushError(parseWebsocket, 3, 'Get unknown operation');
|
||||
logger.error('Get unknown operation', request.op);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -279,7 +320,7 @@ class ParseLiveQueryServer {
|
||||
event: 'ws_disconnect_error',
|
||||
clients: this.clients.size,
|
||||
subscriptions: this.subscriptions.size,
|
||||
error: `Unable to find client ${clientId}`
|
||||
error: `Unable to find client ${clientId}`,
|
||||
});
|
||||
logger.error(`Can not find client ${clientId} on disconnect`);
|
||||
return;
|
||||
@@ -290,12 +331,16 @@ class ParseLiveQueryServer {
|
||||
this.clients.delete(clientId);
|
||||
|
||||
// Delete client from subscriptions
|
||||
for (const [requestId, subscriptionInfo] of _.entries(client.subscriptionInfos)) {
|
||||
for (const [requestId, subscriptionInfo] of _.entries(
|
||||
client.subscriptionInfos
|
||||
)) {
|
||||
const subscription = subscriptionInfo.subscription;
|
||||
subscription.deleteClientSubscription(clientId, requestId);
|
||||
|
||||
// If there is no client which is subscribing this subscription, remove it from subscriptions
|
||||
const classSubscriptions = this.subscriptions.get(subscription.className);
|
||||
const classSubscriptions = this.subscriptions.get(
|
||||
subscription.className
|
||||
);
|
||||
if (!subscription.hasSubscribingClient()) {
|
||||
classSubscriptions.delete(subscription.hash);
|
||||
}
|
||||
@@ -310,14 +355,14 @@ class ParseLiveQueryServer {
|
||||
runLiveQueryEventHandlers({
|
||||
event: 'ws_disconnect',
|
||||
clients: this.clients.size,
|
||||
subscriptions: this.subscriptions.size
|
||||
subscriptions: this.subscriptions.size,
|
||||
});
|
||||
});
|
||||
|
||||
runLiveQueryEventHandlers({
|
||||
event: 'ws_connect',
|
||||
clients: this.clients.size,
|
||||
subscriptions: this.subscriptions.size
|
||||
subscriptions: this.subscriptions.size,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -341,80 +386,86 @@ class ParseLiveQueryServer {
|
||||
}
|
||||
|
||||
const subscriptionSessionToken = subscriptionInfo.sessionToken;
|
||||
return this.sessionTokenCache.getUserId(subscriptionSessionToken).then((userId) => {
|
||||
return acl.getReadAccess(userId);
|
||||
}).then((isSubscriptionSessionTokenMatched) => {
|
||||
if (isSubscriptionSessionTokenMatched) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
// Check if the user has any roles that match the ACL
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// Resolve false right away if the acl doesn't have any roles
|
||||
const acl_has_roles = Object.keys(acl.permissionsById).some(key => key.startsWith("role:"));
|
||||
if (!acl_has_roles) {
|
||||
return resolve(false);
|
||||
return this.sessionTokenCache
|
||||
.getUserId(subscriptionSessionToken)
|
||||
.then(userId => {
|
||||
return acl.getReadAccess(userId);
|
||||
})
|
||||
.then(isSubscriptionSessionTokenMatched => {
|
||||
if (isSubscriptionSessionTokenMatched) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
this.sessionTokenCache.getUserId(subscriptionSessionToken)
|
||||
.then((userId) => {
|
||||
// Check if the user has any roles that match the ACL
|
||||
return new Promise((resolve, reject) => {
|
||||
// Resolve false right away if the acl doesn't have any roles
|
||||
const acl_has_roles = Object.keys(acl.permissionsById).some(key =>
|
||||
key.startsWith('role:')
|
||||
);
|
||||
if (!acl_has_roles) {
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
// Pass along a null if there is no user id
|
||||
if (!userId) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
// Prepare a user object to query for roles
|
||||
// To eliminate a query for the user, create one locally with the id
|
||||
var user = new Parse.User();
|
||||
user.id = userId;
|
||||
return user;
|
||||
|
||||
})
|
||||
.then((user) => {
|
||||
|
||||
// Pass along an empty array (of roles) if no user
|
||||
if (!user) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
// Then get the user's roles
|
||||
var rolesQuery = new Parse.Query(Parse.Role);
|
||||
rolesQuery.equalTo("users", user);
|
||||
return rolesQuery.find({useMasterKey:true});
|
||||
}).
|
||||
then((roles) => {
|
||||
|
||||
// Finally, see if any of the user's roles allow them read access
|
||||
for (const role of roles) {
|
||||
if (acl.getRoleReadAccess(role)) {
|
||||
return resolve(true);
|
||||
this.sessionTokenCache
|
||||
.getUserId(subscriptionSessionToken)
|
||||
.then(userId => {
|
||||
// Pass along a null if there is no user id
|
||||
if (!userId) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
resolve(false);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
|
||||
// Prepare a user object to query for roles
|
||||
// To eliminate a query for the user, create one locally with the id
|
||||
var user = new Parse.User();
|
||||
user.id = userId;
|
||||
return user;
|
||||
})
|
||||
.then(user => {
|
||||
// Pass along an empty array (of roles) if no user
|
||||
if (!user) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
// Then get the user's roles
|
||||
var rolesQuery = new Parse.Query(Parse.Role);
|
||||
rolesQuery.equalTo('users', user);
|
||||
return rolesQuery.find({ useMasterKey: true });
|
||||
})
|
||||
.then(roles => {
|
||||
// Finally, see if any of the user's roles allow them read access
|
||||
for (const role of roles) {
|
||||
if (acl.getRoleReadAccess(role)) {
|
||||
return resolve(true);
|
||||
}
|
||||
}
|
||||
resolve(false);
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
})
|
||||
.then(isRoleMatched => {
|
||||
if (isRoleMatched) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
// Check client sessionToken matches ACL
|
||||
const clientSessionToken = client.sessionToken;
|
||||
return this.sessionTokenCache
|
||||
.getUserId(clientSessionToken)
|
||||
.then(userId => {
|
||||
return acl.getReadAccess(userId);
|
||||
});
|
||||
|
||||
});
|
||||
}).then((isRoleMatched) => {
|
||||
|
||||
if(isRoleMatched) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
// Check client sessionToken matches ACL
|
||||
const clientSessionToken = client.sessionToken;
|
||||
return this.sessionTokenCache.getUserId(clientSessionToken).then((userId) => {
|
||||
return acl.getReadAccess(userId);
|
||||
});
|
||||
}).then((isMatched) => {
|
||||
return Promise.resolve(isMatched);
|
||||
}, () => {
|
||||
return Promise.resolve(false);
|
||||
});
|
||||
})
|
||||
.then(
|
||||
isMatched => {
|
||||
return Promise.resolve(isMatched);
|
||||
},
|
||||
() => {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_handleConnect(parseWebsocket: any, request: any): any {
|
||||
@@ -433,19 +484,22 @@ class ParseLiveQueryServer {
|
||||
runLiveQueryEventHandlers({
|
||||
event: 'connect',
|
||||
clients: this.clients.size,
|
||||
subscriptions: this.subscriptions.size
|
||||
subscriptions: this.subscriptions.size,
|
||||
});
|
||||
}
|
||||
|
||||
_hasMasterKey(request: any, validKeyPairs: any): boolean {
|
||||
if(!validKeyPairs || validKeyPairs.size == 0 ||
|
||||
!validKeyPairs.has("masterKey")) {
|
||||
if (
|
||||
!validKeyPairs ||
|
||||
validKeyPairs.size == 0 ||
|
||||
!validKeyPairs.has('masterKey')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if(!request || !request.hasOwnProperty("masterKey")) {
|
||||
if (!request || !request.hasOwnProperty('masterKey')) {
|
||||
return false;
|
||||
}
|
||||
return request.masterKey === validKeyPairs.get("masterKey");
|
||||
return request.masterKey === validKeyPairs.get('masterKey');
|
||||
}
|
||||
|
||||
_validateKeys(request: any, validKeyPairs: any): boolean {
|
||||
@@ -466,8 +520,14 @@ class ParseLiveQueryServer {
|
||||
_handleSubscribe(parseWebsocket: any, request: any): any {
|
||||
// If we can not find this client, return error to client
|
||||
if (!parseWebsocket.hasOwnProperty('clientId')) {
|
||||
Client.pushError(parseWebsocket, 2, 'Can not find this client, make sure you connect to server before subscribing');
|
||||
logger.error('Can not find this client, make sure you connect to server before subscribing');
|
||||
Client.pushError(
|
||||
parseWebsocket,
|
||||
2,
|
||||
'Can not find this client, make sure you connect to server before subscribing'
|
||||
);
|
||||
logger.error(
|
||||
'Can not find this client, make sure you connect to server before subscribing'
|
||||
);
|
||||
return;
|
||||
}
|
||||
const client = this.clients.get(parseWebsocket.clientId);
|
||||
@@ -484,13 +544,17 @@ class ParseLiveQueryServer {
|
||||
if (classSubscriptions.has(subscriptionHash)) {
|
||||
subscription = classSubscriptions.get(subscriptionHash);
|
||||
} else {
|
||||
subscription = new Subscription(className, request.query.where, subscriptionHash);
|
||||
subscription = new Subscription(
|
||||
className,
|
||||
request.query.where,
|
||||
subscriptionHash
|
||||
);
|
||||
classSubscriptions.set(subscriptionHash, subscription);
|
||||
}
|
||||
|
||||
// Add subscriptionInfo to client
|
||||
const subscriptionInfo = {
|
||||
subscription: subscription
|
||||
subscription: subscription,
|
||||
};
|
||||
// Add selected fields and sessionToken for this subscription if necessary
|
||||
if (request.query.fields) {
|
||||
@@ -502,16 +566,23 @@ class ParseLiveQueryServer {
|
||||
client.addSubscriptionInfo(request.requestId, subscriptionInfo);
|
||||
|
||||
// Add clientId to subscription
|
||||
subscription.addClientSubscription(parseWebsocket.clientId, request.requestId);
|
||||
subscription.addClientSubscription(
|
||||
parseWebsocket.clientId,
|
||||
request.requestId
|
||||
);
|
||||
|
||||
client.pushSubscribe(request.requestId);
|
||||
|
||||
logger.verbose(`Create client ${parseWebsocket.clientId} new subscription: ${request.requestId}`);
|
||||
logger.verbose(
|
||||
`Create client ${parseWebsocket.clientId} new subscription: ${
|
||||
request.requestId
|
||||
}`
|
||||
);
|
||||
logger.verbose('Current client number: %d', this.clients.size);
|
||||
runLiveQueryEventHandlers({
|
||||
event: 'subscribe',
|
||||
clients: this.clients.size,
|
||||
subscriptions: this.subscriptions.size
|
||||
subscriptions: this.subscriptions.size,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -520,27 +591,54 @@ class ParseLiveQueryServer {
|
||||
this._handleSubscribe(parseWebsocket, request);
|
||||
}
|
||||
|
||||
_handleUnsubscribe(parseWebsocket: any, request: any, notifyClient: bool = true): any {
|
||||
_handleUnsubscribe(
|
||||
parseWebsocket: any,
|
||||
request: any,
|
||||
notifyClient: boolean = true
|
||||
): any {
|
||||
// If we can not find this client, return error to client
|
||||
if (!parseWebsocket.hasOwnProperty('clientId')) {
|
||||
Client.pushError(parseWebsocket, 2, 'Can not find this client, make sure you connect to server before unsubscribing');
|
||||
logger.error('Can not find this client, make sure you connect to server before unsubscribing');
|
||||
Client.pushError(
|
||||
parseWebsocket,
|
||||
2,
|
||||
'Can not find this client, make sure you connect to server before unsubscribing'
|
||||
);
|
||||
logger.error(
|
||||
'Can not find this client, make sure you connect to server before unsubscribing'
|
||||
);
|
||||
return;
|
||||
}
|
||||
const requestId = request.requestId;
|
||||
const client = this.clients.get(parseWebsocket.clientId);
|
||||
if (typeof client === 'undefined') {
|
||||
Client.pushError(parseWebsocket, 2, 'Cannot find client with clientId ' + parseWebsocket.clientId +
|
||||
'. Make sure you connect to live query server before unsubscribing.');
|
||||
Client.pushError(
|
||||
parseWebsocket,
|
||||
2,
|
||||
'Cannot find client with clientId ' +
|
||||
parseWebsocket.clientId +
|
||||
'. Make sure you connect to live query server before unsubscribing.'
|
||||
);
|
||||
logger.error('Can not find this client ' + parseWebsocket.clientId);
|
||||
return;
|
||||
}
|
||||
|
||||
const subscriptionInfo = client.getSubscriptionInfo(requestId);
|
||||
if (typeof subscriptionInfo === 'undefined') {
|
||||
Client.pushError(parseWebsocket, 2, 'Cannot find subscription with clientId ' + parseWebsocket.clientId +
|
||||
' subscriptionId ' + requestId + '. Make sure you subscribe to live query server before unsubscribing.');
|
||||
logger.error('Can not find subscription with clientId ' + parseWebsocket.clientId + ' subscriptionId ' + requestId);
|
||||
Client.pushError(
|
||||
parseWebsocket,
|
||||
2,
|
||||
'Cannot find subscription with clientId ' +
|
||||
parseWebsocket.clientId +
|
||||
' subscriptionId ' +
|
||||
requestId +
|
||||
'. Make sure you subscribe to live query server before unsubscribing.'
|
||||
);
|
||||
logger.error(
|
||||
'Can not find subscription with clientId ' +
|
||||
parseWebsocket.clientId +
|
||||
' subscriptionId ' +
|
||||
requestId
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -562,7 +660,7 @@ class ParseLiveQueryServer {
|
||||
runLiveQueryEventHandlers({
|
||||
event: 'unsubscribe',
|
||||
clients: this.clients.size,
|
||||
subscriptions: this.subscriptions.size
|
||||
subscriptions: this.subscriptions.size,
|
||||
});
|
||||
|
||||
if (!notifyClient) {
|
||||
@@ -571,10 +669,12 @@ class ParseLiveQueryServer {
|
||||
|
||||
client.pushUnsubscribe(request.requestId);
|
||||
|
||||
logger.verbose(`Delete client: ${parseWebsocket.clientId} | subscription: ${request.requestId}`);
|
||||
logger.verbose(
|
||||
`Delete client: ${parseWebsocket.clientId} | subscription: ${
|
||||
request.requestId
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
ParseLiveQueryServer
|
||||
}
|
||||
export { ParseLiveQueryServer };
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { loadAdapter } from '../Adapters/AdapterLoader';
|
||||
import {
|
||||
EventEmitterPubSub
|
||||
} from '../Adapters/PubSub/EventEmitterPubSub';
|
||||
import { EventEmitterPubSub } from '../Adapters/PubSub/EventEmitterPubSub';
|
||||
|
||||
import {
|
||||
RedisPubSub
|
||||
} from '../Adapters/PubSub/RedisPubSub';
|
||||
import { RedisPubSub } from '../Adapters/PubSub/RedisPubSub';
|
||||
|
||||
const ParsePubSub = {};
|
||||
|
||||
@@ -18,26 +14,32 @@ ParsePubSub.createPublisher = function(config: any): any {
|
||||
if (useRedis(config)) {
|
||||
return RedisPubSub.createPublisher(config);
|
||||
} else {
|
||||
const adapter = loadAdapter(config.pubSubAdapter, EventEmitterPubSub, config)
|
||||
const adapter = loadAdapter(
|
||||
config.pubSubAdapter,
|
||||
EventEmitterPubSub,
|
||||
config
|
||||
);
|
||||
if (typeof adapter.createPublisher !== 'function') {
|
||||
throw 'pubSubAdapter should have createPublisher()';
|
||||
}
|
||||
return adapter.createPublisher(config);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ParsePubSub.createSubscriber = function(config: any): void {
|
||||
if (useRedis(config)) {
|
||||
return RedisPubSub.createSubscriber(config);
|
||||
} else {
|
||||
const adapter = loadAdapter(config.pubSubAdapter, EventEmitterPubSub, config)
|
||||
const adapter = loadAdapter(
|
||||
config.pubSubAdapter,
|
||||
EventEmitterPubSub,
|
||||
config
|
||||
);
|
||||
if (typeof adapter.createSubscriber !== 'function') {
|
||||
throw 'pubSubAdapter should have createSubscriber()';
|
||||
}
|
||||
return adapter.createSubscriber(config);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
ParsePubSub
|
||||
}
|
||||
export { ParsePubSub };
|
||||
|
||||
@@ -4,21 +4,25 @@ const typeMap = new Map([['disconnect', 'close']]);
|
||||
const getWS = function() {
|
||||
try {
|
||||
return require('uws');
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
return require('ws');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export class ParseWebSocketServer {
|
||||
server: Object;
|
||||
|
||||
constructor(server: any, onConnect: Function, websocketTimeout: number = 10 * 1000) {
|
||||
constructor(
|
||||
server: any,
|
||||
onConnect: Function,
|
||||
websocketTimeout: number = 10 * 1000
|
||||
) {
|
||||
const WebSocketServer = getWS().Server;
|
||||
const wss = new WebSocketServer({ server: server });
|
||||
wss.on('listening', () => {
|
||||
logger.info('Parse LiveQuery Server starts running');
|
||||
});
|
||||
wss.on('connection', (ws) => {
|
||||
wss.on('connection', ws => {
|
||||
onConnect(new ParseWebSocket(ws));
|
||||
// Send ping to client periodically
|
||||
const pingIntervalId = setInterval(() => {
|
||||
|
||||
@@ -55,8 +55,8 @@ function queryHash(query) {
|
||||
if (query instanceof Parse.Query) {
|
||||
query = {
|
||||
className: query.className,
|
||||
where: query._where
|
||||
}
|
||||
where: query._where,
|
||||
};
|
||||
}
|
||||
var where = flattenOrQueries(query.where || {});
|
||||
var columns = [];
|
||||
@@ -99,8 +99,10 @@ function contains(haystack: Array, needle: any): boolean {
|
||||
if (typeof ptr === 'string' && ptr === needle.objectId) {
|
||||
return true;
|
||||
}
|
||||
if (ptr.className === needle.className &&
|
||||
ptr.objectId === needle.objectId) {
|
||||
if (
|
||||
ptr.className === needle.className &&
|
||||
ptr.objectId === needle.objectId
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -117,7 +119,7 @@ function contains(haystack: Array, needle: any): boolean {
|
||||
function matchesQuery(object: any, query: any): boolean {
|
||||
if (query instanceof Parse.Query) {
|
||||
var className =
|
||||
(object.id instanceof Id) ? object.id.className : object.className;
|
||||
object.id instanceof Id ? object.id.className : object.className;
|
||||
if (className !== query.className) {
|
||||
return false;
|
||||
}
|
||||
@@ -144,7 +146,6 @@ function equalObjectsGeneric(obj, compareTo, eqlFn) {
|
||||
return eqlFn(obj, compareTo);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Determines whether an object matches a single key's constraints
|
||||
*/
|
||||
@@ -152,12 +153,16 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
if (constraints === null) {
|
||||
return false;
|
||||
}
|
||||
if(key.indexOf(".") >= 0){
|
||||
if (key.indexOf('.') >= 0) {
|
||||
// Key references a subobject
|
||||
var keyComponents = key.split(".");
|
||||
var keyComponents = key.split('.');
|
||||
var subObjectKey = keyComponents[0];
|
||||
var keyRemainder = keyComponents.slice(1).join(".");
|
||||
return matchesKeyConstraints(object[subObjectKey] || {}, keyRemainder, constraints);
|
||||
var keyRemainder = keyComponents.slice(1).join('.');
|
||||
return matchesKeyConstraints(
|
||||
object[subObjectKey] || {},
|
||||
keyRemainder,
|
||||
constraints
|
||||
);
|
||||
}
|
||||
var i;
|
||||
if (key === '$or') {
|
||||
@@ -191,7 +196,11 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
});
|
||||
}
|
||||
|
||||
return equalObjectsGeneric(object[key], Parse._decode(key, constraints), equalObjects);
|
||||
return equalObjectsGeneric(
|
||||
object[key],
|
||||
Parse._decode(key, constraints),
|
||||
equalObjects
|
||||
);
|
||||
}
|
||||
// More complex cases
|
||||
for (var condition in constraints) {
|
||||
@@ -200,124 +209,131 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
compareTo = Parse._decode(key, compareTo);
|
||||
}
|
||||
switch (condition) {
|
||||
case '$lt':
|
||||
if (object[key] >= compareTo) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$lte':
|
||||
if (object[key] > compareTo) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$gt':
|
||||
if (object[key] <= compareTo) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$gte':
|
||||
if (object[key] < compareTo) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$ne':
|
||||
if (equalObjects(object[key], compareTo)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$in':
|
||||
if (!contains(compareTo, object[key])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$nin':
|
||||
if (contains(compareTo, object[key])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$all':
|
||||
for (i = 0; i < compareTo.length; i++) {
|
||||
if (object[key].indexOf(compareTo[i]) < 0) {
|
||||
case '$lt':
|
||||
if (object[key] >= compareTo) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$lte':
|
||||
if (object[key] > compareTo) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$gt':
|
||||
if (object[key] <= compareTo) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$gte':
|
||||
if (object[key] < compareTo) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$ne':
|
||||
if (equalObjects(object[key], compareTo)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$in':
|
||||
if (!contains(compareTo, object[key])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$nin':
|
||||
if (contains(compareTo, object[key])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$all':
|
||||
for (i = 0; i < compareTo.length; i++) {
|
||||
if (object[key].indexOf(compareTo[i]) < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '$exists': {
|
||||
const propertyExists = typeof object[key] !== 'undefined';
|
||||
const existenceIsRequired = constraints['$exists'];
|
||||
if (typeof constraints['$exists'] !== 'boolean') {
|
||||
// The SDK will never submit a non-boolean for $exists, but if someone
|
||||
// tries to submit a non-boolean for $exits outside the SDKs, just ignore it.
|
||||
break;
|
||||
}
|
||||
if (
|
||||
(!propertyExists && existenceIsRequired) ||
|
||||
(propertyExists && !existenceIsRequired)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case '$exists': {
|
||||
const propertyExists = typeof object[key] !== 'undefined';
|
||||
const existenceIsRequired = constraints['$exists'];
|
||||
if (typeof constraints['$exists'] !== 'boolean') {
|
||||
// The SDK will never submit a non-boolean for $exists, but if someone
|
||||
// tries to submit a non-boolean for $exits outside the SDKs, just ignore it.
|
||||
break;
|
||||
}
|
||||
if ((!propertyExists && existenceIsRequired) || (propertyExists && !existenceIsRequired)) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '$regex':
|
||||
if (typeof compareTo === 'object') {
|
||||
return compareTo.test(object[key]);
|
||||
}
|
||||
// JS doesn't support perl-style escaping
|
||||
var expString = '';
|
||||
var escapeEnd = -2;
|
||||
var escapeStart = compareTo.indexOf('\\Q');
|
||||
while (escapeStart > -1) {
|
||||
// Add the unescaped portion
|
||||
expString += compareTo.substring(escapeEnd + 2, escapeStart);
|
||||
escapeEnd = compareTo.indexOf('\\E', escapeStart);
|
||||
if (escapeEnd > -1) {
|
||||
expString += compareTo.substring(escapeStart + 2, escapeEnd)
|
||||
.replace(/\\\\\\\\E/g, '\\E').replace(/\W/g, '\\$&');
|
||||
case '$regex':
|
||||
if (typeof compareTo === 'object') {
|
||||
return compareTo.test(object[key]);
|
||||
}
|
||||
// JS doesn't support perl-style escaping
|
||||
var expString = '';
|
||||
var escapeEnd = -2;
|
||||
var escapeStart = compareTo.indexOf('\\Q');
|
||||
while (escapeStart > -1) {
|
||||
// Add the unescaped portion
|
||||
expString += compareTo.substring(escapeEnd + 2, escapeStart);
|
||||
escapeEnd = compareTo.indexOf('\\E', escapeStart);
|
||||
if (escapeEnd > -1) {
|
||||
expString += compareTo
|
||||
.substring(escapeStart + 2, escapeEnd)
|
||||
.replace(/\\\\\\\\E/g, '\\E')
|
||||
.replace(/\W/g, '\\$&');
|
||||
}
|
||||
|
||||
escapeStart = compareTo.indexOf('\\Q', escapeEnd);
|
||||
}
|
||||
expString += compareTo.substring(Math.max(escapeStart, escapeEnd + 2));
|
||||
var exp = new RegExp(expString, constraints.$options || '');
|
||||
if (!exp.test(object[key])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$nearSphere':
|
||||
if (!compareTo || !object[key]) {
|
||||
return false;
|
||||
}
|
||||
var distance = compareTo.radiansTo(object[key]);
|
||||
var max = constraints.$maxDistance || Infinity;
|
||||
return distance <= max;
|
||||
case '$within':
|
||||
if (!compareTo || !object[key]) {
|
||||
return false;
|
||||
}
|
||||
var southWest = compareTo.$box[0];
|
||||
var northEast = compareTo.$box[1];
|
||||
if (southWest.latitude > northEast.latitude ||
|
||||
southWest.longitude > northEast.longitude) {
|
||||
// Invalid box, crosses the date line
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
object[key].latitude > southWest.latitude &&
|
||||
escapeStart = compareTo.indexOf('\\Q', escapeEnd);
|
||||
}
|
||||
expString += compareTo.substring(Math.max(escapeStart, escapeEnd + 2));
|
||||
var exp = new RegExp(expString, constraints.$options || '');
|
||||
if (!exp.test(object[key])) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case '$nearSphere':
|
||||
if (!compareTo || !object[key]) {
|
||||
return false;
|
||||
}
|
||||
var distance = compareTo.radiansTo(object[key]);
|
||||
var max = constraints.$maxDistance || Infinity;
|
||||
return distance <= max;
|
||||
case '$within':
|
||||
if (!compareTo || !object[key]) {
|
||||
return false;
|
||||
}
|
||||
var southWest = compareTo.$box[0];
|
||||
var northEast = compareTo.$box[1];
|
||||
if (
|
||||
southWest.latitude > northEast.latitude ||
|
||||
southWest.longitude > northEast.longitude
|
||||
) {
|
||||
// Invalid box, crosses the date line
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
object[key].latitude > southWest.latitude &&
|
||||
object[key].latitude < northEast.latitude &&
|
||||
object[key].longitude > southWest.longitude &&
|
||||
object[key].longitude < northEast.longitude
|
||||
);
|
||||
case '$options':
|
||||
// Not a query type, but a way to add options to $regex. Ignore and
|
||||
// avoid the default
|
||||
break;
|
||||
case '$maxDistance':
|
||||
// Not a query type, but a way to add a cap to $nearSphere. Ignore and
|
||||
// avoid the default
|
||||
break;
|
||||
case '$select':
|
||||
return false;
|
||||
case '$dontSelect':
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
);
|
||||
case '$options':
|
||||
// Not a query type, but a way to add options to $regex. Ignore and
|
||||
// avoid the default
|
||||
break;
|
||||
case '$maxDistance':
|
||||
// Not a query type, but a way to add a cap to $nearSphere. Ignore and
|
||||
// avoid the default
|
||||
break;
|
||||
case '$select':
|
||||
return false;
|
||||
case '$dontSelect':
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -325,7 +341,7 @@ function matchesKeyConstraints(object, key, constraints) {
|
||||
|
||||
var QueryTools = {
|
||||
queryHash: queryHash,
|
||||
matchesQuery: matchesQuery
|
||||
matchesQuery: matchesQuery,
|
||||
};
|
||||
|
||||
module.exports = QueryTools;
|
||||
|
||||
@@ -1,141 +1,141 @@
|
||||
const general = {
|
||||
'title': 'General request schema',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'op': {
|
||||
'type': 'string',
|
||||
'enum': ['connect', 'subscribe', 'unsubscribe', 'update']
|
||||
title: 'General request schema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
op: {
|
||||
type: 'string',
|
||||
enum: ['connect', 'subscribe', 'unsubscribe', 'update'],
|
||||
},
|
||||
},
|
||||
'required': ['op']
|
||||
required: ['op'],
|
||||
};
|
||||
|
||||
const connect = {
|
||||
'title': 'Connect operation schema',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'op': 'connect',
|
||||
'applicationId': {
|
||||
'type': 'string'
|
||||
const connect = {
|
||||
title: 'Connect operation schema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
op: 'connect',
|
||||
applicationId: {
|
||||
type: 'string',
|
||||
},
|
||||
'javascriptKey': {
|
||||
type: 'string'
|
||||
javascriptKey: {
|
||||
type: 'string',
|
||||
},
|
||||
'masterKey': {
|
||||
type: 'string'
|
||||
masterKey: {
|
||||
type: 'string',
|
||||
},
|
||||
'clientKey': {
|
||||
type: 'string'
|
||||
clientKey: {
|
||||
type: 'string',
|
||||
},
|
||||
'windowsKey': {
|
||||
type: 'string'
|
||||
windowsKey: {
|
||||
type: 'string',
|
||||
},
|
||||
'restAPIKey': {
|
||||
'type': 'string'
|
||||
restAPIKey: {
|
||||
type: 'string',
|
||||
},
|
||||
sessionToken: {
|
||||
type: 'string',
|
||||
},
|
||||
'sessionToken': {
|
||||
'type': 'string'
|
||||
}
|
||||
},
|
||||
'required': ['op', 'applicationId'],
|
||||
"additionalProperties": false
|
||||
required: ['op', 'applicationId'],
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
const subscribe = {
|
||||
'title': 'Subscribe operation schema',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'op': 'subscribe',
|
||||
'requestId': {
|
||||
'type': 'number'
|
||||
title: 'Subscribe operation schema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
op: 'subscribe',
|
||||
requestId: {
|
||||
type: 'number',
|
||||
},
|
||||
'query': {
|
||||
'title': 'Query field schema',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'className': {
|
||||
'type': 'string'
|
||||
query: {
|
||||
title: 'Query field schema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
className: {
|
||||
type: 'string',
|
||||
},
|
||||
'where': {
|
||||
'type': 'object'
|
||||
where: {
|
||||
type: 'object',
|
||||
},
|
||||
'fields': {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
fields: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
}
|
||||
minItems: 1,
|
||||
uniqueItems: true,
|
||||
},
|
||||
},
|
||||
'required': ['where', 'className'],
|
||||
'additionalProperties': false
|
||||
required: ['where', 'className'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
sessionToken: {
|
||||
type: 'string',
|
||||
},
|
||||
'sessionToken': {
|
||||
'type': 'string'
|
||||
}
|
||||
},
|
||||
'required': ['op', 'requestId', 'query'],
|
||||
'additionalProperties': false
|
||||
required: ['op', 'requestId', 'query'],
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
const update = {
|
||||
'title': 'Update operation schema',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'op': 'update',
|
||||
'requestId': {
|
||||
'type': 'number'
|
||||
title: 'Update operation schema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
op: 'update',
|
||||
requestId: {
|
||||
type: 'number',
|
||||
},
|
||||
'query': {
|
||||
'title': 'Query field schema',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'className': {
|
||||
'type': 'string'
|
||||
query: {
|
||||
title: 'Query field schema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
className: {
|
||||
type: 'string',
|
||||
},
|
||||
'where': {
|
||||
'type': 'object'
|
||||
where: {
|
||||
type: 'object',
|
||||
},
|
||||
'fields': {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
fields: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string',
|
||||
},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true
|
||||
}
|
||||
minItems: 1,
|
||||
uniqueItems: true,
|
||||
},
|
||||
},
|
||||
'required': ['where', 'className'],
|
||||
'additionalProperties': false
|
||||
required: ['where', 'className'],
|
||||
additionalProperties: false,
|
||||
},
|
||||
sessionToken: {
|
||||
type: 'string',
|
||||
},
|
||||
'sessionToken': {
|
||||
'type': 'string'
|
||||
}
|
||||
},
|
||||
'required': ['op', 'requestId', 'query'],
|
||||
'additionalProperties': false
|
||||
required: ['op', 'requestId', 'query'],
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
const unsubscribe = {
|
||||
'title': 'Unsubscribe operation schema',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'op': 'unsubscribe',
|
||||
'requestId': {
|
||||
'type': 'number'
|
||||
}
|
||||
title: 'Unsubscribe operation schema',
|
||||
type: 'object',
|
||||
properties: {
|
||||
op: 'unsubscribe',
|
||||
requestId: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
'required': ['op', 'requestId'],
|
||||
"additionalProperties": false
|
||||
}
|
||||
required: ['op', 'requestId'],
|
||||
additionalProperties: false,
|
||||
};
|
||||
|
||||
const RequestSchema = {
|
||||
'general': general,
|
||||
'connect': connect,
|
||||
'subscribe': subscribe,
|
||||
'update': update,
|
||||
'unsubscribe': unsubscribe
|
||||
}
|
||||
general: general,
|
||||
connect: connect,
|
||||
subscribe: subscribe,
|
||||
update: update,
|
||||
unsubscribe: unsubscribe,
|
||||
};
|
||||
|
||||
export default RequestSchema;
|
||||
|
||||
@@ -2,24 +2,27 @@ import Parse from 'parse/node';
|
||||
import LRU from 'lru-cache';
|
||||
import logger from '../logger';
|
||||
|
||||
function userForSessionToken(sessionToken){
|
||||
var q = new Parse.Query("_Session");
|
||||
q.equalTo("sessionToken", sessionToken);
|
||||
return q.first({useMasterKey:true}).then(function(session){
|
||||
if(!session){
|
||||
return Promise.reject("No session found for session token");
|
||||
function userForSessionToken(sessionToken) {
|
||||
var q = new Parse.Query('_Session');
|
||||
q.equalTo('sessionToken', sessionToken);
|
||||
return q.first({ useMasterKey: true }).then(function(session) {
|
||||
if (!session) {
|
||||
return Promise.reject('No session found for session token');
|
||||
}
|
||||
return session.get("user");
|
||||
return session.get('user');
|
||||
});
|
||||
}
|
||||
|
||||
class SessionTokenCache {
|
||||
cache: Object;
|
||||
|
||||
constructor(timeout: number = 30 * 24 * 60 * 60 * 1000, maxSize: number = 10000) {
|
||||
constructor(
|
||||
timeout: number = 30 * 24 * 60 * 60 * 1000,
|
||||
maxSize: number = 10000
|
||||
) {
|
||||
this.cache = new LRU({
|
||||
max: maxSize,
|
||||
maxAge: timeout
|
||||
maxAge: timeout,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,21 +32,34 @@ class SessionTokenCache {
|
||||
}
|
||||
const userId = this.cache.get(sessionToken);
|
||||
if (userId) {
|
||||
logger.verbose('Fetch userId %s of sessionToken %s from Cache', userId, sessionToken);
|
||||
logger.verbose(
|
||||
'Fetch userId %s of sessionToken %s from Cache',
|
||||
userId,
|
||||
sessionToken
|
||||
);
|
||||
return Promise.resolve(userId);
|
||||
}
|
||||
return userForSessionToken(sessionToken).then((user) => {
|
||||
logger.verbose('Fetch userId %s of sessionToken %s from Parse', user.id, sessionToken);
|
||||
const userId = user.id;
|
||||
this.cache.set(sessionToken, userId);
|
||||
return Promise.resolve(userId);
|
||||
}, (error) => {
|
||||
logger.error('Can not fetch userId for sessionToken %j, error %j', sessionToken, error);
|
||||
return Promise.reject(error);
|
||||
});
|
||||
return userForSessionToken(sessionToken).then(
|
||||
user => {
|
||||
logger.verbose(
|
||||
'Fetch userId %s of sessionToken %s from Parse',
|
||||
user.id,
|
||||
sessionToken
|
||||
);
|
||||
const userId = user.id;
|
||||
this.cache.set(sessionToken, userId);
|
||||
return Promise.resolve(userId);
|
||||
},
|
||||
error => {
|
||||
logger.error(
|
||||
'Can not fetch userId for sessionToken %j, error %j',
|
||||
sessionToken,
|
||||
error
|
||||
);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
SessionTokenCache
|
||||
}
|
||||
export { SessionTokenCache };
|
||||
|
||||
@@ -34,7 +34,11 @@ class Subscription {
|
||||
|
||||
const index = requestIds.indexOf(requestId);
|
||||
if (index < 0) {
|
||||
logger.error('Can not find client %d subscription %d to delete', clientId, requestId);
|
||||
logger.error(
|
||||
'Can not find client %d subscription %d to delete',
|
||||
clientId,
|
||||
requestId
|
||||
);
|
||||
return;
|
||||
}
|
||||
requestIds.splice(index, 1);
|
||||
@@ -49,6 +53,4 @@ class Subscription {
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
Subscription
|
||||
}
|
||||
export { Subscription };
|
||||
|
||||
@@ -9,14 +9,14 @@ function equalObjects(a, b) {
|
||||
return false;
|
||||
}
|
||||
if (typeof a !== 'object') {
|
||||
return (a === b);
|
||||
return a === b;
|
||||
}
|
||||
if (a === b) {
|
||||
return true;
|
||||
}
|
||||
if (toString.call(a) === '[object Date]') {
|
||||
if (toString.call(b) === '[object Date]') {
|
||||
return (+a === +b);
|
||||
return +a === +b;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user