Last active
May 15, 2021 21:20
-
-
Save ahmedengu/607266b5caeffbc19c4508b4684b4b7b to your computer and use it in GitHub Desktop.
ParseLiveQuery support for codenameone/parse4cn1 require cn1-webSockets , parse4cn1 || LiveQuery setup: https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery#server-setup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.parse4cn1; | |
import ca.weblite.codename1.json.JSONException; | |
import ca.weblite.codename1.json.JSONObject; | |
import com.codename1.io.websocket.WebSocket; | |
import com.codename1.io.websocket.WebSocketState; | |
import com.parse4cn1.*; | |
import java.util.HashMap; | |
import java.util.Map; | |
/** | |
* Created by ahmedengu. | |
*/ | |
public abstract class ParseLiveQuery { | |
private final static String OP = "op"; | |
private JSONObject query; | |
private String subscribeData; | |
private int requestId; | |
// LiveQuery subscription id | |
private static int requestIds = 0; | |
private static String liveQueryServerURL; | |
private static WebSocket webSocket; | |
private static Map<Integer, ParseLiveQuery> lQuerys = new HashMap<>(); | |
// optional to handle the websocket events and errors that not related to a specific query | |
private static WsCallback wsCallback = new WsCallback() { | |
@Override | |
public void error(String op, int code, String error, boolean reconnect) { | |
System.out.println(op + ", code:" + code + ", error:" + error); | |
} | |
}; | |
// query callback event | |
public abstract void event(String op, int requestId, ParseObject object); | |
// subscribe to a ParseQuery | |
public ParseLiveQuery(ParseQuery query) throws JSONException, ParseException { | |
this(query.encode()); | |
} | |
// subscribe to a JSONObject query | |
// you should provide classname , where , fields [optional] | |
private ParseLiveQuery(JSONObject query) throws JSONException, ParseException { | |
this.query = query; | |
requestIds++; | |
requestId = requestIds; | |
subscribe(); | |
} | |
// initialzie the websocket connection to the liveQury server | |
// when it's open its gonna send the connect message | |
// when it's connected it gonna subscribe to all the available queries [in case there was a disconnection] | |
//onClose: if it not triggered using .close() it gonna try to reconnect 5 times | |
private static void init() { | |
webSocket = new WebSocket(getLiveQueryServerURL()) { | |
@Override | |
protected void onOpen() { | |
this.send(getConnect()); | |
wsCallback.onOpen(); | |
} | |
@Override | |
protected void onClose(int i, String s) { | |
for (int j = 0; s != null && j < 5; j++) | |
try { | |
Thread.sleep(3000); | |
init(); | |
Thread.sleep(1000); | |
return; | |
} catch (Exception e1) { | |
e1.printStackTrace(); | |
} | |
wsCallback.onClose(i, s); | |
} | |
@Override | |
protected void onMessage(String s) { | |
try { | |
JSONObject json = new JSONObject(s); | |
String operation = json.getString(OP); | |
switch (operation) { | |
case "connected": | |
for (Map.Entry<Integer, ParseLiveQuery> entry : lQuerys.entrySet()) | |
webSocket.send(entry.getValue().getSubscribe()); | |
break; | |
case "subscribed": | |
break; | |
case "unsubscribed": | |
break; | |
case "error": | |
wsCallback.error(operation, json.getInt("code"), json.getString("error"), json.getBoolean("reconnect")); | |
break; | |
default: | |
JSONObject data = json.getJSONObject("object"); | |
ParseObject parseObject = ParseObject.create(data.getString("className")); | |
parseObject.setData(data); | |
int requestId = json.getInt("requestId"); | |
lQuerys.get(requestId).event(operation, requestId, parseObject); | |
break; | |
} | |
} catch (JSONException e) { | |
onError(e); | |
} | |
wsCallback.onMessage(s); | |
} | |
@Override | |
protected void onMessage(byte[] bytes) { | |
wsCallback.onMessage(bytes); | |
} | |
@Override | |
protected void onError(Exception e) { | |
e.printStackTrace(); | |
wsCallback.onError(e); | |
} | |
}; | |
webSocket.connect(); | |
} | |
//liveQuery connect message | |
private static String getConnect() { | |
JSONObject output = new JSONObject(); | |
try { | |
output.put(OP, "connect"); | |
if (ParseUser.getCurrent() != null) | |
output.put("sessionToken", ParseUser.getCurrent().getSessionToken()); | |
output.put("clientKey", Parse.getClientKey()); | |
output.put("applicationId", Parse.getApplicationId()); | |
} catch (JSONException ex) { | |
ex.printStackTrace(); | |
} | |
return output.toString(); | |
} | |
//liveQuery subscribe message | |
private String getSubscribe() throws JSONException { | |
if (subscribeData == null) { | |
JSONObject output = new JSONObject(); | |
output.put(OP, "subscribe"); | |
output.put("requestId", this.requestId); | |
JSONObject q = new JSONObject(); | |
q.put("className", query.getString("className")); | |
q.put("where", query.get("where")); | |
if (query.has("keys")) | |
q.put("fields", query.get("keys")); | |
if (query.has("fields")) | |
q.put("fields", query.get("fields")); | |
output.put("query", q); | |
if (ParseUser.getCurrent() != null) | |
output.put("sessionToken", ParseUser.getCurrent().getSessionToken()); | |
subscribeData = output.toString(); | |
} | |
return subscribeData; | |
} | |
//liveQuery unsubscribe message | |
public void unsubscribe() throws Exception { | |
unsubscribe(true); | |
} | |
public void unsubscribe(boolean remove) throws Exception { | |
if (remove) | |
lQuerys.remove(requestId); | |
JSONObject output = new JSONObject(); | |
output.put(OP, "unsubscribe"); | |
output.put("requestId", requestId); | |
if (webSocket != null && output != null && (remove || webSocket.getReadyState() == WebSocketState.OPEN)) | |
webSocket.send(output.toString()); | |
} | |
//subscribe action .. gonna be called in the constructor , but it's public in case there was a disconnection and you want to resubscribe | |
public void subscribe() throws ParseException, JSONException { | |
lQuerys.put(requestId, this); | |
if (webSocket == null) | |
init(); | |
else if (webSocket.getReadyState() != WebSocketState.OPEN) | |
webSocket.connect(); | |
else | |
webSocket.send(getSubscribe()); | |
} | |
//unsubscribe from all then close the websocket | |
public static void close() throws Exception { | |
if (webSocket != null) { | |
for (Map.Entry<Integer, ParseLiveQuery> entry : lQuerys.entrySet()) | |
entry.getValue().unsubscribe(false); | |
lQuerys.clear(); | |
requestIds = 0; | |
webSocket.close(); | |
webSocket = null; | |
} | |
} | |
// LiveQuery server URL from the parse server URL | |
public static String getLiveQueryServerURL() { | |
if (liveQueryServerURL == null) { | |
String endpoint = Parse.getApiEndpoint(); | |
String portcol = (endpoint.indexOf("https") == 0) ? "wss" : "ws"; | |
liveQueryServerURL = portcol + ((endpoint.indexOf("https") == 0) ? endpoint.substring(5) : endpoint.substring(4)); | |
} | |
return liveQueryServerURL; | |
} | |
public int getRequestId() { | |
return requestId; | |
} | |
public JSONObject getQuery() { | |
return query; | |
} | |
public static WsCallback getWsCallback() { | |
return wsCallback; | |
} | |
// optional to handle the websocket events and errors that not related to a specific query | |
public static void setWsCallback(WsCallback wsCallback) { | |
ParseLiveQuery.wsCallback = wsCallback; | |
} | |
public static abstract class WsCallback { | |
public abstract void error(String op, int code, String error, boolean reconnect); | |
public void onOpen() { | |
} | |
; | |
public void onClose(int var1, String var2) { | |
} | |
; | |
public void onMessage(String var1) { | |
} | |
; | |
public void onMessage(byte[] var1) { | |
} | |
; | |
public void onError(Exception var1) { | |
} | |
; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package userclasses; | |
import ca.weblite.codename1.json.JSONException; | |
import com.parse4cn1.*; | |
import generated.StateMachineBase; | |
import java.util.HashMap; | |
/** | |
************ | |
** You can find a simple usage exmple below | |
************ | |
* Dont forget to checkout livequery setup wiki: https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery#server-setup | |
* CN1 websockets: https://github.com/shannah/cn1-websockets | |
* Parse4CN1: https://github.com/sidiabale/parse4cn1 | |
*/ | |
public class StateMachine extends StateMachineBase { | |
public StateMachine(String resFile) { | |
super(resFile); | |
} | |
protected void initVars(Resources res) { | |
Parse.initialize("http://localhost:1337/parse", "myAppId", "master"); | |
} | |
@Override | |
protected void beforeTest(Form f) { | |
// optional to handle the websocket events and errors that not related to a specific query | |
ParseLiveQuery.setWsCallback(new ParseLiveQuery.WsCallback() { | |
@Override | |
public void error(String op, int code, String error, boolean reconnect) { | |
} | |
@Override | |
public void onOpen() { | |
super.onOpen(); | |
} | |
@Override | |
public void onClose(int var1, String var2) { | |
super.onClose(var1, var2); | |
} | |
// This method called whenever there's a message in the websocket | |
@Override | |
public void onMessage(String var1) { | |
super.onMessage(var1); | |
} | |
@Override | |
public void onMessage(byte[] var1) { | |
super.onMessage(var1); | |
} | |
@Override | |
public void onError(Exception var1) { | |
super.onError(var1); | |
} | |
}); | |
try { | |
// query support $lt, $lte, $gt, $gte, $ne, $in, $nin, $exists, $all, $regex, $nearSphere, $within :: https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery-Protocol-Specification | |
ParseQuery<ParseObject> query = ParseQuery.getQuery("GameScore"); | |
query.whereEqualTo("playerName", "Sean Plott").whereEqualTo("cheatMode", false); | |
// subscribe | |
ParseLiveQuery liveQuery = new ParseLiveQuery(query) { | |
@Override | |
// this method called when there is an event | |
// https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery#events | |
public void event(String op, int requestId, ParseObject object) { | |
System.out.println(op + " " + object.getObjectId()); | |
} | |
}; | |
// unsubscribe from a spacific query | |
liveQuery.unsubscribe(); | |
// close the Websocket and unsubscribe from all queries | |
ParseLiveQuery.close(); | |
} catch (JSONException e) { | |
e.printStackTrace(); | |
} catch (ParseException e) { | |
e.printStackTrace(); | |
} | |
} | |
} |
Greetings @blessingmobile,
Thank you for your feedback it's really appreciated and sorry for my late reply.
I have included a link to liveQuery setup and added some comments to the code as you suggested hopefully it's more clear now.
Kindly, feel free to comment any improvements or to fork the snippet.
Best Regards,
Ahmed
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Good day Ahmed,
Many thanks for this API integration. The example code is great, however I spent some time getting it to work not realising that I need to register the Classes in order for Live Query to work in the index.js Parse Server file. Like so:
liveQuery: {
classNames: ["Posts", "Comments", "GameScore"] // List of classes to support for query subscriptions
},
Can you make a note of this somewhere maybe in the example and also mention that the client side "onMessage()" callback is called every time there is an update from the server.