├── .gitignore
├── .travis.yml
├── README.md
├── build.gradle
├── dependencies.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── java
├── build.gradle
├── maven.gradle
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── scaledrone
│ │ └── lib
│ │ ├── AuthenticationListener.java
│ │ ├── HistoryRoomListener.java
│ │ ├── Listener.java
│ │ ├── Member.java
│ │ ├── Message.java
│ │ ├── ObservableRoomListener.java
│ │ ├── Room.java
│ │ ├── RoomListener.java
│ │ ├── Scaledrone.java
│ │ ├── SubscribeOptions.java
│ │ └── messagetypes
│ │ ├── Authenticate.java
│ │ ├── GenericCallback.java
│ │ ├── Handshake.java
│ │ ├── Publish.java
│ │ ├── Subscribe.java
│ │ └── Unsubscribe.java
│ └── test
│ └── java
│ └── com
│ └── scaledrone
│ └── lib
│ ├── AuthTest.java
│ ├── BaseTest.java
│ ├── CloseTest.java
│ ├── HistoryTest.java
│ ├── MessagingTest.java
│ └── ObservableRoomsTest.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/osx,java,gradle,intellij
3 |
4 | ### Intellij ###
5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
6 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
7 |
8 | .idea
9 |
10 | # CMake
11 | cmake-build-debug/
12 |
13 | # Mongo Explorer plugin:
14 | .idea/**/mongoSettings.xml
15 |
16 | ## File-based project format:
17 | *.iws
18 |
19 | ## Plugin-specific files:
20 |
21 | # IntelliJ
22 | /out/
23 |
24 | # mpeltonen/sbt-idea plugin
25 | .idea_modules/
26 |
27 | # JIRA plugin
28 | atlassian-ide-plugin.xml
29 |
30 | # Cursive Clojure plugin
31 | .idea/replstate.xml
32 |
33 | # Ruby plugin and RubyMine
34 | /.rakeTasks
35 |
36 | # Crashlytics plugin (for Android Studio and IntelliJ)
37 | com_crashlytics_export_strings.xml
38 | crashlytics.properties
39 | crashlytics-build.properties
40 | fabric.properties
41 |
42 | ### Intellij Patch ###
43 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
44 |
45 | # *.iml
46 | # modules.xml
47 | # .idea/misc.xml
48 | # *.ipr
49 |
50 | # Sonarlint plugin
51 | .idea/sonarlint
52 |
53 | ### Java ###
54 | # Compiled class file
55 | *.class
56 |
57 | # Log file
58 | *.log
59 |
60 | # BlueJ files
61 | *.ctxt
62 |
63 | # Mobile Tools for Java (J2ME)
64 | .mtj.tmp/
65 |
66 | # Package Files #
67 | *.jar
68 | *.war
69 | *.ear
70 | *.zip
71 | *.tar.gz
72 | *.rar
73 |
74 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
75 | hs_err_pid*
76 |
77 | ### OSX ###
78 | *.DS_Store
79 | .AppleDouble
80 | .LSOverride
81 |
82 | # Icon must end with two \r
83 | Icon
84 |
85 | # Thumbnails
86 | ._*
87 |
88 | # Files that might appear in the root of a volume
89 | .DocumentRevisions-V100
90 | .fseventsd
91 | .Spotlight-V100
92 | .TemporaryItems
93 | .Trashes
94 | .VolumeIcon.icns
95 | .com.apple.timemachine.donotpresent
96 |
97 | # Directories potentially created on remote AFP share
98 | .AppleDB
99 | .AppleDesktop
100 | Network Trash Folder
101 | Temporary Items
102 | .apdisk
103 |
104 | ### Gradle ###
105 | .gradle
106 | **/build/
107 |
108 | # Ignore Gradle GUI config
109 | gradle-app.setting
110 |
111 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
112 | !gradle-wrapper.jar
113 |
114 | # Cache of project
115 | .gradletasknamecache
116 |
117 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898
118 | # gradle/wrapper/gradle-wrapper.properties
119 |
120 | # End of https://www.gitignore.io/api/osx,java,gradle,intellij
121 | java/src/test/java/com/scaledrone/lib/Stress.java
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | sudo: false
3 | jdk:
4 | - oraclejdk8
5 | script:
6 | - ./gradlew assemble
7 | - ./gradlew test --stacktrace
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/ScaleDrone/scaledrone-java)
2 |
3 | # Scaledrone Java/Android WebSocket client
4 |
5 | > Use the Scaledrone Java client to connect to the Scaledrone realtime messaging service
6 |
7 | This project goes hand to hand with [Scaledrone documentation](https://www.scaledrone.com/docs). It's still a work in progress, pull requests and issues are very welcome.
8 |
9 | ## Installation
10 |
11 | [  ](https://bintray.com/scaledrone/scaledrone/scaledrone-java/_latestVersion)
12 |
13 | The library is hosted on the [Jcenter repository](https://bintray.com/scaledrone/scaledrone/scaledrone-java), so you need to ensure that the repo is referenced also; IDEs will typically include this by default:
14 |
15 | ```
16 | repositories {
17 | jcenter()
18 | }
19 | ```
20 |
21 | ### Maven
22 |
23 | ```xml
24 |
25 | com.scaledrone
26 | scaledrone-java
27 | 0.7.0
28 | pom
29 |
30 | ```
31 |
32 | ### Gradle
33 |
34 | ```
35 | compile 'com.scaledrone:scaledrone-java:0.7.0'
36 | ```
37 |
38 | ## Android
39 |
40 | If you are using this library on Android make sure you add the [INTERNET](https://developer.android.com/training/basics/network-ops/connecting.html) permission to the manifest file.
41 | ```xml
42 |
43 | ```
44 |
45 | ## Getting started
46 |
47 | ### Basic example
48 |
49 | ```java
50 | final Scaledrone drone = new Scaledrone("CHANNEL_ID_FROM_DASHBOARD");
51 | drone.connect(new Listener() {
52 | @Override
53 | public void onOpen() {
54 | drone.subscribe("myroom", new RoomListener() {
55 | @Override
56 | public void onOpen(Room room) {
57 | room.publish("Hello world");
58 | }
59 |
60 | @Override
61 | public void onOpenFailure(Room room, Exception ex) {
62 | // This can happen when you don't have correct permissions
63 | System.out.println("Failed to subscribe to room: " + ex.getMessage());
64 | }
65 |
66 | @Override
67 | public void onMessage(Room room, Message message) {
68 | System.out.println("Message: " + message.getData().asText());
69 | }
70 | });
71 | }
72 |
73 | @Override
74 | public void onOpenFailure(Exception ex) {
75 | System.out.println("Failed to open connection: " + ex.getMessage());
76 | }
77 |
78 | @Override
79 | public void onFailure(Exception ex) {
80 | System.out.println("Unexcpected failure: " + ex.getMessage());
81 | }
82 |
83 | @Override
84 | public void onClosed(String reason) {
85 | System.out.println("Connection closed: " + reason);
86 | }
87 | });
88 | ```
89 |
90 | ### Connection
91 |
92 | Connecting to a new channel is done by creating a new instance of `Scaledrone` and calling the `connect()` method. To create your own `channel_id` create a Scaledrone account and a new channel from [Scaledrone dashboard](https://dashboard.scaledrone.com/channels).
93 |
94 | ```java
95 | final Scaledrone drone = new Scaledrone("channel_id");
96 | drone.connect(new Listener() {
97 | // overwrite the Listener methods
98 | });
99 | ```
100 |
101 | ### Subscribing to messages
102 |
103 | Subscribing to messages is done by subscribing to a room. Rooms are used to group users into groups, to which we can then publish messages. You can subscribe to multiple rooms.
104 |
105 | A received message is of type `JsonNode`. Jackson `JsonNode` can easily be transferred into POJO or String.
106 |
107 | ```java
108 | drone.subscribe("myroom", new RoomListener() {
109 | @Override
110 | public void onMessage(Room room, Message message) {
111 | // parse as string
112 | System.out.println("Message: " + message.getData().asText());
113 |
114 | // or parse as POJO
115 | try {
116 | ObjectMapper mapper = new ObjectMapper();
117 | Pojo pojo = mapper.treeToValue(message.getData(), Pojo.class);
118 | System.out.println("Message: " + pojo);
119 | } catch (JsonProcessingException e) {
120 | e.printStackTrace();
121 | }
122 | }
123 | // overwrite other methods
124 | });
125 | ```
126 |
127 | ### Publishing messages
128 |
129 | A published message will be converted to JSON using Jackson. You don't have to be subscribed to a room when publishing to it.
130 |
131 | ```java
132 | drone.publish("roomName", data);
133 | // or
134 | room.publish(data);
135 | ```
136 |
137 | ## Authentication
138 |
139 | Using authentication is optional, it can be enabled from the channel settings page. Scaledrone uses JSON Web Token for authentication.
140 |
141 | A typical authentication flow happens after connecting to a channel. At that point the user would send the `drone.getClientID()` to your personal authentication server which returns a JSON Web Token. The token is then passed on to Scaledrone.
142 |
143 | Use the [jwt.io debugger](https://jwt.io) to help you debug your JWT tokens.
144 |
145 | ```java
146 | final Scaledrone drone = new Scaledrone("channel_id");
147 | drone.connect(new Listener() {
148 | @Override
149 | public void onOpen() {
150 | final String jwt = requestJWTFromYourAuthServer(drone.getClientID());
151 | drone.authenticate(jwt, new AuthenticationListener() {
152 | @Override
153 | public void onAuthentication() {
154 | // user is now connected to the channel
155 | }
156 |
157 | @Override
158 | public void onAuthenticationFailure(Exception ex) {
159 | // something went wront, probably a problem with the JWT
160 | }
161 | });
162 | }
163 | // overwrite methods
164 | ```
165 |
166 | See the Scaledrone [JWT authentication docs](https://www.scaledrone.com/docs/jwt-authentication) on what the token should look like.
167 | See the [authentication tests](https://github.com/ScaleDrone/scaledrone-java/blob/master/src/test/java/com/scaledrone/AuthTest.java#L159-L178) to see how to generate a JWT on the server side.
168 |
169 | ## Observable rooms
170 |
171 | Observable rooms act like regular rooms but provide additional events for keeping track of connected members.
172 | Observable room names need to be prefixed with `observable-`.
173 |
174 | ```java
175 | final Scaledrone drone = new Scaledrone("channel_id", data);
176 | drone.connect(new Listener() {
177 | @Override
178 | public void onOpen() {
179 | Room room = drone.subscribe("observable-myroom", new RoomListener() {
180 | // Overwrite regular room listener methods
181 | });
182 | room.listenToObservableEvents(listener, new ObservableRoomListener() {
183 | @Override
184 | public void onMembers(Room room, ArrayList members) {
185 | // Emits an array of members that have joined the room. This event is only triggered once, right after the user has successfully connected to the observable room.
186 | // Keep in mind that the session user will also be part of this array, so the minimum size of the array is 1
187 |
188 | Member member = members.get(0);
189 | ObjectMapper mapper = new ObjectMapper();
190 | try {
191 | Data d = mapper.treeToValue(member.getClientData(), Data.class);
192 | } catch (JsonProcessingException e) {
193 |
194 | }
195 | }
196 |
197 | @Override
198 | public void onMemberJoin(Room room, Member member) {
199 | // A new member joined the room.
200 | }
201 |
202 | @Override
203 | public void onMemberLeave(Room room, Member member) {
204 | // A member left the room (or disconnected)
205 | });
206 | });
207 | }
208 | ```
209 |
210 | ## Message history
211 |
212 | When creating a Scaledrone room you can supply the number of messages to recieve from that room's history. The messages will arrive, in reverse chronological order and one by one, in `scaledroneRoomDidReceiveMessage`, just like real-time messages.
213 |
214 | Pass the `SubscribeOptions` as the second parameter to the `subscribe` method to define how many messages you would like to receive.
215 |
216 | You can then listen to the history messages using `room.listenToHistoryEvents()`.
217 |
218 | In order to recieve message history messages, this feature needs to be enabled in the [Scaledrone dashboard](http://dashboard.scaledrone.com). You can learn more about Message History and its limitations in [Scaledrone docs](https://www.scaledrone.com/docs/message-history).
219 |
220 | ```java
221 |
222 | Room room = drone.subscribe(roomName, new RoomListener() {
223 | // implement the default RoomListener methods here
224 | }, new SubscribeOptions(50)); // ask for 50 messages from the history
225 |
226 | room.listenToHistoryEvents(new HistoryRoomListener() {
227 | @Override
228 | public void onHistoryMessage(Room room, Message message) {
229 | System.out.println('Received a message from the past ' + message.getData().asText());
230 | }
231 | });
232 | ```
233 |
234 | ## Checking if the messages was sent by the user itself
235 |
236 | ```java
237 | // inside a room listener
238 | @Override
239 | public void onMessage(Room room, Message message) {
240 | if (message.getClientID() == scaledrone.getClientID()) {
241 | // message is sent by session user
242 | }
243 | }
244 | ```
245 |
246 | ## Reconnecting
247 | The currect version of the Java library doesn't reconnect automatically after a possible disconnect. This feature will be added in the future.
248 | To handle reconnection you can listen to the `onFailure` event.
249 | ```java
250 | @Override
251 | public void onFailure(Exception ex) {
252 | tryReconnecting(0);
253 | }
254 |
255 | private void tryReconnecting(final int reconnectAttempt) {
256 | Timer timer = new Timer();
257 | timer.schedule(new TimerTask() {
258 | @Override
259 | public void run() {
260 | final Scaledrone drone = new Scaledrone("channel-id");
261 | drone.connect(new Listener() {
262 | @Override
263 | public void onFailure(Exception ex) {
264 | tryReconnecting(reconnectAttempt + 1);
265 | }
266 | });
267 | // set everything up again..
268 | }
269 | }, reconnectAttempt * 1000);
270 | }
271 | ```
272 |
273 | ## Troubleshooting
274 | ```
275 | PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
276 | ```
277 | This likely means that your Java doesn't support Let's Encrypt Certificates. Upgrade Java7 to 7u111 or newer or Java 8 to 8u101 or newer. [Read more..](https://stackoverflow.com/questions/34110426/does-java-support-lets-encrypt-certificates/35454903)
278 |
279 | ## Publishing a new version to JCenter
280 |
281 | * Update the version 1.2.3 in `build.gradle`
282 | * Run `./gradlew java:assembleRelease` to generate the new release files
283 | * Go to https://bintray.com/scaledrone/scaledrone/scaledrone-java and create a new version
284 | * Click "Upload Files"
285 | * Type in `com/scaledrone/scaledrone-java/1.2.3` into "Target Repository Path" ensuring the correct version is included.
286 | * Upload all the files from `java/build/release/1.2.3/com/scaledrone/scaledrone-java/1.2.3`
287 | * You will see a notice "You have 8 unpublished item(s) for this version", click "Publish". It might take a few minutes
288 | * Update the README with the correct version
289 |
290 | ## Changelog
291 |
292 | * `0.7.0` - Fixed parsing 'id' field from JSON message.
293 | * `0.6.0` - Added message history features. Created a `Message` class that wraps the sent data, member as well as new properties such as message ID, timestamp and clientID.
294 | * `0.5.0` - Add up `close()` method.
295 | * `0.4.0` - Hook up `onFailure` listener. This can be used for reconnecting.
296 | * `0.3.0` - Add `member` parameter to `onMessage` listener method.
297 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | }
5 | dependencies {
6 | classpath 'com.android.tools.build:gradle:2.1.3'
7 | }
8 | }
9 |
10 | allprojects {
11 | group = 'com.scaledrone'
12 |
13 | repositories {
14 | jcenter()
15 | }
16 |
17 | tasks.withType(Javadoc) {
18 | // To prevent javadoc warnings with Java 8
19 | options.addStringOption('Xdoclint:none', '-quiet')
20 | }
21 | }
22 |
23 | subprojects {
24 | version = '0.7.0'
25 | description = """Scaledrone Java client library"""
26 | }
27 |
--------------------------------------------------------------------------------
/dependencies.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | testCompile group: 'junit', name: 'junit', version: '4.12'
3 | testCompile 'com.auth0:java-jwt:2.3.0'
4 | testCompile 'net.jodah:concurrentunit:0.4.3'
5 | compile 'com.squareup.okhttp3:okhttp:3.9.1'
6 | compile 'com.fasterxml.jackson.core:jackson-core:2.9.0'
7 | compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.0'
8 | compile 'com.fasterxml.jackson.core:jackson-databind:2.9.0'
9 | }
10 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ScaleDrone/scaledrone-java/8fbb32dfecdbbf5e27859b829fc1d2052572de72/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Jan 01 09:54:09 EET 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/java/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'de.fuerstenau.buildconfig' version '1.1.7'
3 | }
4 |
5 | apply plugin: 'java'
6 | apply from: 'maven.gradle'
7 |
8 | sourceCompatibility = 1.7
9 | targetCompatibility = 1.7
10 |
11 | apply from: '../dependencies.gradle'
12 |
13 | buildConfig {
14 | packageName 'com.scaledrone.lib'
15 | clsName 'BuildConfig'
16 | buildConfigField 'String', 'LIBRARY_NAME', 'java'
17 | }
18 |
19 | sourceSets {
20 | main {
21 | java {
22 | srcDirs = ['src/main/java', '../lib/src/main/java']
23 | }
24 | }
25 | test {
26 | java {
27 | srcDirs = ['src/test/java', '../lib/src/test/java']
28 | }
29 | }
30 | }
31 |
32 | // Default jar: add com.scaledrone classes from :lib dependency.
33 | jar {
34 | baseName = 'scaledrone-java'
35 | from {
36 | configurations.compile.collect {
37 | it.isDirectory() ? it : zipTree(it)
38 | }
39 | }
40 | includes = ['**/com/scaledrone/**']
41 | includeEmptyDirs false
42 | exclude 'META-INF/**'
43 | }
44 |
45 | // fullJar: add all classes from dependencies transitively.
46 | task fullJar(type: Jar) {
47 | baseName = 'scaledrone-java'
48 | classifier = 'full'
49 | from {
50 | configurations.compile.collect {
51 | it.isDirectory() ? it : zipTree(it)
52 | }
53 | }
54 | with jar
55 | exclude 'META-INF/**'
56 | }
57 |
58 | assemble.dependsOn fullJar
59 |
60 | configurations {
61 | fullConfiguration
62 | testsConfiguration
63 | }
64 |
65 | artifacts {
66 | fullConfiguration fullJar
67 | }
68 |
69 | task testRealtimeSuite(type: Test) {
70 | filter {
71 | includeTestsMatching "*RealtimeSuite"
72 | }
73 | beforeTest { descriptor ->
74 | logger.lifecycle("Running test: " + descriptor)
75 | }
76 | outputs.upToDateWhen { false }
77 | }
78 |
79 | task testRestSuite(type: Test) {
80 | filter {
81 | includeTestsMatching "*RestSuite"
82 | }
83 | beforeTest { descriptor ->
84 | logger.lifecycle("Running test: " + descriptor)
85 | }
86 | outputs.upToDateWhen { false }
87 | }
88 |
--------------------------------------------------------------------------------
/java/maven.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'maven'
3 | apply plugin: 'signing'
4 |
5 | def groupId = "com.scaledrone"
6 | def artifactId = "scaledrone-java"
7 | def localReleaseDest = "${buildDir}/release/${version}"
8 |
9 | uploadArchives {
10 | repositories.mavenDeployer {
11 | pom.groupId = groupId
12 | pom.artifactId = artifactId
13 | pom.version = version
14 |
15 | // Add other pom properties here if you want (developer details / licenses)
16 | pom.project {
17 | name 'Scaledrone Java client library'
18 | description 'A Java client library for Scaledrone realtime messaging service and platform.'
19 | packaging 'jar'
20 | inceptionYear '2018'
21 | scm {
22 | url 'scm:git:https://github.com/ScaleDrone/scaledrone-java'
23 | connection 'scm:git:https://github.com/ScaleDrone/scaledrone-java'
24 | developerConnection 'scm:git@github.com:ScaleDrone/scaledrone-java.git'
25 | }
26 | organization {
27 | name 'Scaledrone'
28 | url 'https://www.scaledrone.com/'
29 | }
30 | issueManagement {
31 | system 'Github'
32 | url 'https://github.com/ScaleDrone/scaledrone-java/issues'
33 | }
34 | licenses {
35 | license {
36 | name 'The Apache Software License, Version 2.0'
37 | url 'https://raw.githubusercontent.com/ScaleDrone/scaledrone-java/master/LICENSE.md'
38 | distribution 'repo'
39 | }
40 | }
41 | }
42 |
43 | // Exclude test dependencies
44 | pom.whenConfigured {
45 | p -> p.dependencies = p.dependencies.findAll {
46 | dep -> dep.scope == 'compile'
47 | }
48 | }
49 |
50 | // Export files to local storage
51 | repository(url: "file://${localReleaseDest}")
52 | }
53 | }
54 |
55 | task zipRelease(type: Zip) {
56 | from localReleaseDest
57 | destinationDir buildDir
58 | archiveName "release-${version}.zip"
59 | }
60 |
61 | task assembleRelease << {
62 | println "Release ${version} can be found at ${localReleaseDest}/"
63 | println "Release ${version} zipped can be found ${buildDir}/release-${version}.zip"
64 | }
65 |
66 | assembleRelease.dependsOn(uploadArchives)
67 | assembleRelease.dependsOn(zipRelease)
68 |
69 | task sourcesJar(type: Jar) {
70 | classifier = 'sources'
71 | from sourceSets.main.allSource
72 | }
73 |
74 | task javadocJar(type: Jar, dependsOn: javadoc) {
75 | classifier = 'javadoc'
76 | from javadoc.destinationDir
77 | }
78 |
79 | artifacts {
80 | archives sourcesJar
81 | archives javadocJar
82 | }
83 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/AuthenticationListener.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | public interface AuthenticationListener {
4 | void onAuthentication();
5 | void onAuthenticationFailure(Exception ex);
6 | }
7 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/HistoryRoomListener.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | public interface HistoryRoomListener {
4 | void onHistoryMessage(Room room, Message message);
5 | }
6 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/Listener.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | public interface Listener {
4 | void onOpen();
5 | void onOpenFailure(Exception ex);
6 | void onFailure(Exception ex);
7 | void onClosed(String reason);
8 | }
9 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/Member.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4 | import com.fasterxml.jackson.databind.JsonNode;
5 |
6 | @JsonIgnoreProperties(ignoreUnknown = true)
7 | public class Member {
8 |
9 | private String id;
10 | private JsonNode authData;
11 | private JsonNode clientData;
12 |
13 | public String getId() {
14 | return id;
15 | }
16 |
17 | public JsonNode getAuthData() {
18 | return authData;
19 | }
20 |
21 | public JsonNode getClientData() {
22 | return clientData;
23 | }
24 |
25 | public void setId(String id) {
26 | this.id = id;
27 | }
28 |
29 | @Override
30 | public String toString() {
31 | return "Member{" +
32 | "id='" + id + '\'' +
33 | ", authData=" + authData +
34 | ", clientData=" + clientData +
35 | '}';
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/Message.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import com.fasterxml.jackson.databind.JsonNode;
4 |
5 | public class Message {
6 | private String ID;
7 | private JsonNode data;
8 | private long timestamp;
9 | private String clientID;
10 | private Member member;
11 |
12 | public Message(String ID, JsonNode data, long timestamp, String clientID, Member member) {
13 | this.ID = ID;
14 | this.data = data;
15 | this.timestamp = timestamp;
16 | this.clientID = clientID;
17 | this.member = member;
18 | }
19 |
20 | public String getID() {
21 | return ID;
22 | }
23 |
24 | public JsonNode getData() {
25 | return data;
26 | }
27 |
28 | public long getTimestamp() {
29 | return timestamp;
30 | }
31 |
32 | public String getClientID() {
33 | return clientID;
34 | }
35 |
36 | public Member getMember() {
37 | return member;
38 | }
39 |
40 | @Override
41 | public String toString() {
42 | return "Message{" +
43 | "ID='" + ID + '\'' +
44 | ", data=" + data +
45 | ", timestamp=" + timestamp +
46 | ", clientID='" + clientID + '\'' +
47 | ", member=" + member +
48 | '}';
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/ObservableRoomListener.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import java.util.ArrayList;
4 |
5 | public interface ObservableRoomListener {
6 | void onMembers(Room room, ArrayList members);
7 | void onMemberJoin(Room room, Member member);
8 | void onMemberLeave(Room room, Member member);
9 | }
10 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/Room.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | public class Room {
9 | private String name;
10 | private RoomListener listener;
11 | private ObservableRoomListener observableListener;
12 | private HistoryRoomListener historyListener;
13 | private Map members = new HashMap();
14 | private Scaledrone scaledrone;
15 |
16 | private int historyNextIndex;
17 | private Message[] historyReceivedMessages;
18 |
19 | public Room(String name, RoomListener listener, Scaledrone drone, SubscribeOptions options) {
20 | this.name = name;
21 | this.listener = listener;
22 | this.scaledrone = drone;
23 | if (options.getHistoryCount() != null && options.getHistoryCount() > 0) {
24 | this.historyReceivedMessages = new Message[options.getHistoryCount()];
25 | }
26 | }
27 |
28 | public void handleHistoryMessage(Message message, int index) {
29 | this.historyReceivedMessages[index] = message;
30 | Message nextMessage = this.historyReceivedMessages[this.historyNextIndex];
31 | if (nextMessage != null) {
32 | this.historyNextIndex++;
33 | if (this.getHistoryListener() != null) {
34 | this.getHistoryListener().onHistoryMessage(this, nextMessage);
35 | }
36 | }
37 | }
38 |
39 | public void listenToObservableEvents(ObservableRoomListener listener) {
40 | this.observableListener = listener;
41 | }
42 |
43 | public void listenToHistoryEvents(HistoryRoomListener listener) {
44 | this.historyListener = listener;
45 | }
46 |
47 | public void publish(Object message) {
48 | this.scaledrone.publish(this.getName(), message);
49 | }
50 |
51 | public String getName() {
52 | return name;
53 | }
54 |
55 | public RoomListener getListener() {
56 | return listener;
57 | }
58 |
59 | public ObservableRoomListener getObservableListener() {
60 | return observableListener;
61 | }
62 |
63 | public HistoryRoomListener getHistoryListener() {
64 | return historyListener;
65 | }
66 |
67 | public Scaledrone getScaledrone() {
68 | return scaledrone;
69 | }
70 |
71 | public Map getMembers() {
72 | return members;
73 | }
74 |
75 | public void unsubscribe() {
76 | scaledrone.unsubscribe(this);
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/RoomListener.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | public interface RoomListener {
4 | void onOpen(Room room);
5 | void onOpenFailure(Room room, Exception ex);
6 | void onMessage(Room room, Message message);
7 | }
8 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/Scaledrone.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import com.fasterxml.jackson.annotation.JsonInclude;
4 | import com.fasterxml.jackson.core.JsonProcessingException;
5 | import com.fasterxml.jackson.core.type.TypeReference;
6 | import com.fasterxml.jackson.databind.ObjectMapper;
7 | import com.scaledrone.lib.messagetypes.*;
8 | import com.scaledrone.lib.messagetypes.Handshake;
9 | import okhttp3.*;
10 | import okio.ByteString;
11 |
12 | import java.io.IOException;
13 | import java.util.ArrayList;
14 | import java.util.HashMap;
15 | import java.util.Map;
16 |
17 | interface CallbackHandler {
18 | void handleCallback(GenericCallback cb);
19 | void handleError(Exception ex);
20 | }
21 |
22 | public class Scaledrone extends WebSocketListener {
23 | private static final int NORMAL_CLOSURE_STATUS = 1000;
24 | private String channelID;
25 | private String clientID;
26 | private Object data;
27 | private WebSocket ws;
28 | private OkHttpClient client;
29 | private ArrayList callbacks = new ArrayList();
30 | private Map roomsMap = new HashMap();
31 | private Listener listener;
32 | private String url = "wss://api.scaledrone.com/v3/websocket";
33 |
34 | public Scaledrone(String channelID) {
35 | this.channelID = channelID;
36 | }
37 |
38 | public Scaledrone(String channelID, Object data) {
39 | this.channelID = channelID;
40 | this.data = data;
41 | }
42 |
43 | public void connect(Listener listener) {
44 | this.client = new OkHttpClient();
45 | this.listener = listener;
46 | Request request = new Request.Builder().url(url).build();
47 | this.ws = client.newWebSocket(request, this);
48 | client.dispatcher().executorService().shutdown();
49 | }
50 |
51 | @Override
52 | public void onOpen(WebSocket webSocket, Response response) {
53 | Handshake handshake = new Handshake(this.channelID, this.data, this.registerCallback(new CallbackHandler() {
54 | @Override
55 | public void handleCallback(GenericCallback cb) {
56 | Scaledrone.this.clientID = cb.getClientID();
57 | Scaledrone.this.listener.onOpen();
58 | }
59 | @Override
60 | public void handleError(Exception ex) {
61 | Scaledrone.this.listener.onOpenFailure(ex);
62 | }
63 | }));
64 | this.sendMessage(handshake);
65 | }
66 |
67 | private void sendMessage(Object data) {
68 | try {
69 | ObjectMapper mapper = new ObjectMapper();
70 | mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); //omit "null" properties
71 | String json = mapper.writeValueAsString(data);
72 | this.ws.send(json);
73 | } catch (JsonProcessingException e) {
74 | e.printStackTrace();
75 | }
76 | }
77 |
78 | public void publish(String roomName, Object message) {
79 | this.sendMessage(new Publish(roomName, message));
80 | }
81 |
82 | @Override
83 | public void onMessage(WebSocket webSocket, String text) {
84 | ObjectMapper mapper = new ObjectMapper();
85 | try {
86 | GenericCallback cb = mapper.readValue(text, GenericCallback.class);
87 | if (cb.getCallback() != null) {
88 | CallbackHandler handler = this.callbacks.get(cb.getCallback());
89 | this.callbacks.set(cb.getCallback(), null);
90 | if (cb.getError() == null) {
91 | handler.handleCallback(cb);
92 | } else {
93 | handler.handleError(new Exception(cb.getError()));
94 | }
95 | } else if (cb.getError() != null) {
96 | this.listener.onFailure(new Exception(cb.getError()));
97 | } else {
98 | final Room room = this.roomsMap.get(cb.getRoom());
99 | Member member;
100 | Message message;
101 | switch (cb.getType()) {
102 | case "publish":
103 | member = room.getMembers().get(cb.getClientID());
104 | message = new Message(cb.getID(), cb.getMessage(), cb.getTimestamp(), cb.getClientID(), member);
105 | room.getListener().onMessage(room, message);
106 | break;
107 | case "observable_members":
108 | ArrayList members = mapper.readValue(
109 | mapper.treeAsTokens(cb.getData()),
110 | mapper.getTypeFactory().constructType(new TypeReference>(){})
111 | );
112 | for (Member m : members) {
113 | room.getMembers().put(m.getId(), m);
114 | }
115 | if (room.getObservableListener() != null) {
116 | room.getObservableListener().onMembers(room, members);
117 | }
118 | break;
119 | case "observable_member_join":
120 | member = mapper.treeToValue(cb.getData(), Member.class);
121 | room.getMembers().put(member.getId(), member);
122 | if (room.getObservableListener() != null) {
123 | room.getObservableListener().onMemberJoin(room, member);
124 | }
125 | break;
126 | case "observable_member_leave":
127 | member = mapper.treeToValue(cb.getData(), Member.class);
128 | room.getMembers().remove(member.getId());
129 | if (room.getObservableListener() != null) {
130 | room.getObservableListener().onMemberLeave(room, member);
131 | }
132 | break;
133 | case "history_message":
134 | member = room.getMembers().get(cb.getClientID());
135 | message = new Message(cb.getID(), cb.getMessage(), cb.getTimestamp(), cb.getClientID(), member);
136 | if (room.getHistoryListener() != null) {
137 | room.handleHistoryMessage(message, cb.getIndex());
138 | }
139 | break;
140 | }
141 | }
142 | } catch (IOException e) {
143 | e.printStackTrace();
144 | }
145 | }
146 |
147 | public Room subscribe(String roomName, final RoomListener roomListener) {
148 | return subscribe(roomName, roomListener, new SubscribeOptions(null));
149 | }
150 |
151 | public Room subscribe(String roomName, final RoomListener roomListener, SubscribeOptions options) {
152 | final Room room = new Room(roomName, roomListener,this, options);
153 | Subscribe subscribe = new Subscribe(roomName, options.getHistoryCount(), this.registerCallback(new CallbackHandler() {
154 | @Override
155 | public void handleCallback(GenericCallback cb) {
156 | roomListener.onOpen(room);
157 | }
158 | @Override
159 | public void handleError(Exception ex) {
160 | roomListener.onOpenFailure(room, ex);
161 | }
162 | }));
163 | this.sendMessage(subscribe);
164 | this.roomsMap.put(roomName, room);
165 | return room;
166 | }
167 |
168 | public void unsubscribe(Room room) {
169 | Unsubscribe unsubscribe = new Unsubscribe(room.getName(), this.registerCallback(new CallbackHandler() {
170 | @Override
171 | public void handleCallback(GenericCallback cb) {
172 |
173 | }
174 | @Override
175 | public void handleError(Exception ex) {
176 |
177 | }
178 | }));
179 | this.sendMessage(unsubscribe);
180 | }
181 |
182 | public void authenticate(String jwt, final AuthenticationListener listener) {
183 | Authenticate authenticate = new Authenticate(jwt, this.registerCallback(new CallbackHandler() {
184 | @Override
185 | public void handleCallback(GenericCallback cb) {
186 | listener.onAuthentication();
187 | }
188 | @Override
189 | public void handleError(Exception ex) {
190 | listener.onAuthenticationFailure(ex);
191 | }
192 | }));
193 | this.sendMessage(authenticate);
194 | }
195 |
196 | public void close() {
197 | this.ws.close(NORMAL_CLOSURE_STATUS, "");
198 | }
199 |
200 | private Integer registerCallback(CallbackHandler handler) {
201 | this.callbacks.add(handler);
202 | return this.callbacks.size() - 1;
203 | }
204 |
205 | @Override
206 | public void onMessage(WebSocket webSocket, ByteString bytes) {
207 |
208 | }
209 |
210 | @Override
211 | public void onClosing(WebSocket webSocket, int code, String reason) {
212 | System.out.println("Connection is closing, reason: " + reason);
213 | }
214 |
215 | @Override
216 | public void onClosed(WebSocket webSocket, int code, String reason) {
217 | System.out.println("Connection is closed, reason: " + reason);
218 | this.listener.onClosed(reason);
219 | }
220 |
221 | @Override
222 | public void onFailure(WebSocket webSocket, Throwable t, Response response) {
223 | System.out.println("Connection failed: " + response);
224 | this.listener.onFailure(new Exception(t));
225 | }
226 |
227 | public void setUrl(String url) {
228 | this.url = url;
229 | }
230 |
231 | public String getClientID() {
232 | return clientID;
233 | }
234 | }
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/SubscribeOptions.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | public class SubscribeOptions {
4 | private Integer historyCount;
5 |
6 | public SubscribeOptions(Integer historyCount) {
7 | this.historyCount = historyCount;
8 | }
9 |
10 | public void setHistoryCount(int historyCount) {
11 | this.historyCount = historyCount;
12 | }
13 |
14 | public Integer getHistoryCount() {
15 | return historyCount;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/messagetypes/Authenticate.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib.messagetypes;
2 |
3 | public class Authenticate {
4 | public final String type = "authenticate";
5 | public String token;
6 | public Integer callback;
7 |
8 | public Authenticate(String token, Integer callback) {
9 | this.token = token;
10 | this.callback = callback;
11 | }
12 | }
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/messagetypes/GenericCallback.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib.messagetypes;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 | import com.fasterxml.jackson.databind.JsonNode;
6 |
7 | @JsonIgnoreProperties(ignoreUnknown = true)
8 | public class GenericCallback {
9 | // common fields
10 | private String type;
11 | private Integer callback;
12 | private String error;
13 |
14 | // message specific fields
15 | @JsonProperty("id")
16 | private String ID; // message ID
17 | private long timestamp; // unix timestamp of when the message was sent
18 | @JsonProperty("client_id")
19 | private String clientID; // the ID of the client who sent the message
20 | private String room; // the room to which the message was sent
21 | private JsonNode message; // the data content of the message
22 | private JsonNode data; // JSON object or array of objects
23 | private int index; // index of the message in history
24 |
25 | public String getType() {
26 | return type;
27 | }
28 |
29 | public Integer getCallback() {
30 | return callback;
31 | }
32 |
33 | public String getError() {
34 | return error;
35 | }
36 |
37 | public String getClientID() {
38 | return clientID;
39 | }
40 |
41 | public String getRoom() {
42 | return room;
43 | }
44 |
45 | public JsonNode getMessage() {
46 | return message;
47 | }
48 |
49 | public JsonNode getData() {
50 | return data;
51 | }
52 |
53 | public String getID() {
54 | return ID;
55 | }
56 |
57 | public long getTimestamp() {
58 | return timestamp;
59 | }
60 |
61 | public int getIndex() {
62 | return index;
63 | }
64 |
65 | @Override
66 | public String toString() {
67 | return "GenericCallback{" +
68 | "type='" + type + '\'' +
69 | ", callback=" + callback +
70 | ", error='" + error + '\'' +
71 | ", ID='" + ID + '\'' +
72 | ", timestamp=" + timestamp +
73 | ", clientID='" + clientID + '\'' +
74 | ", room='" + room + '\'' +
75 | ", message=" + message +
76 | ", data=" + data +
77 | ", index=" + index +
78 | '}';
79 | }
80 | }
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/messagetypes/Handshake.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib.messagetypes;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class Handshake {
6 | public final String type = "handshake";
7 | public String channel;
8 | public Integer callback;
9 | @JsonProperty("client_data")
10 | public Object clientData; // should this be a string?
11 |
12 | public Handshake(String channel, Object clientData, Integer callback) {
13 | this.channel = channel;
14 | this.clientData = clientData;
15 | this.callback = callback;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/messagetypes/Publish.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib.messagetypes;
2 |
3 | public class Publish {
4 | public final String type = "publish";
5 | public String room;
6 | public Object message;
7 |
8 | public Publish(String room, Object message) {
9 | this.room = room;
10 | this.message = message;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/messagetypes/Subscribe.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib.messagetypes;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class Subscribe {
6 | public final String type = "subscribe";
7 | public String room;
8 | public Integer callback;
9 | @JsonProperty("history_count")
10 | public Integer historyCount;
11 | @JsonProperty("history")
12 | public Integer history; // legacy property
13 |
14 | public Subscribe(String room, Integer historyCount, Integer callback) {
15 | this.room = room;
16 | this.historyCount = this.history = historyCount;
17 | this.callback = callback;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/java/src/main/java/com/scaledrone/lib/messagetypes/Unsubscribe.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib.messagetypes;
2 |
3 | public class Unsubscribe {
4 | public final String type = "unsubscribe";
5 | public String room;
6 | public Integer callback;
7 |
8 | public Unsubscribe(String room, Integer callback) {
9 | this.room = room;
10 | this.callback = callback;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/java/src/test/java/com/scaledrone/lib/AuthTest.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import com.auth0.jwt.JWTSigner;
4 | import com.fasterxml.jackson.databind.JsonNode;
5 | import net.jodah.concurrentunit.Waiter;
6 | import org.junit.Test;
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | class ScaledronePermission {
12 | private boolean publish;
13 | private boolean subscribe;
14 |
15 | public ScaledronePermission(boolean publish, boolean subscribe) {
16 | this.publish = publish;
17 | this.subscribe = subscribe;
18 | }
19 |
20 | public boolean getPublish() {
21 | return publish;
22 | }
23 |
24 | public boolean getSubscribe() {
25 | return subscribe;
26 | }
27 | }
28 |
29 | public class AuthTest {
30 |
31 | final String channel = System.getenv("AUTH_CHANNEL");
32 | final String secret = System.getenv("SECRET");
33 |
34 | @Test
35 | public void connectWithoutAuth() throws Exception {
36 | final Waiter waiter = new Waiter();
37 | final Scaledrone drone = new Scaledrone(channel);
38 | drone.connect(new Listener() {
39 | @Override
40 | public void onOpen() {
41 | Room room = drone.subscribe("room1", new RoomListener() {
42 | @Override
43 | public void onOpen(Room room) {
44 | waiter.fail();
45 | }
46 |
47 | @Override
48 | public void onOpenFailure(Room room, Exception ex) {
49 | waiter.assertEquals("Unauthorized", ex.getMessage());
50 | waiter.resume();
51 | }
52 |
53 | @Override
54 | public void onMessage(Room room, Message message) {
55 | waiter.fail();
56 | }
57 | });
58 | }
59 |
60 | @Override
61 | public void onOpenFailure(Exception ex) {
62 | waiter.fail(ex.getMessage());
63 | }
64 |
65 | @Override
66 | public void onFailure(Exception ex) {
67 | waiter.fail(ex.getMessage());
68 | }
69 |
70 | @Override
71 | public void onClosed(String reason) {
72 | waiter.fail(reason);
73 | }
74 | });
75 |
76 | waiter.await(10000);
77 | }
78 |
79 | @Test
80 | public void connectWithWrongJWT() throws Exception {
81 | final Waiter waiter = new Waiter();
82 | final Scaledrone drone = new Scaledrone(channel);
83 | drone.connect(new Listener() {
84 | @Override
85 | public void onOpen() {
86 | drone.authenticate("invalid jwt string", new AuthenticationListener() {
87 | @Override
88 | public void onAuthentication() {
89 | waiter.fail();
90 | }
91 |
92 | @Override
93 | public void onAuthenticationFailure(Exception ex) {
94 | waiter.assertEquals("JWT does not have 3 parts", ex.getMessage());
95 | waiter.resume();
96 | }
97 | });
98 | }
99 |
100 | @Override
101 | public void onOpenFailure(Exception ex) {
102 | waiter.fail(ex.getMessage());
103 | }
104 |
105 | @Override
106 | public void onFailure(Exception ex) {
107 | waiter.fail(ex.getMessage());
108 | }
109 |
110 | @Override
111 | public void onClosed(String reason) {
112 | waiter.fail(reason);
113 | }
114 | });
115 |
116 | waiter.await(10000);
117 | }
118 |
119 | @Test
120 | public void connectWithCorrectJWT() throws Exception {
121 | final Waiter waiter = new Waiter();
122 | final Scaledrone drone = new Scaledrone(channel);
123 | drone.connect(new Listener() {
124 | @Override
125 | public void onOpen() {
126 | final String jwt = generateJWT(drone.getClientID());
127 | drone.authenticate(jwt, new AuthenticationListener() {
128 | @Override
129 | public void onAuthentication() {
130 | waiter.resume();
131 | }
132 |
133 | @Override
134 | public void onAuthenticationFailure(Exception ex) {
135 | waiter.fail(ex);
136 | }
137 | });
138 | }
139 |
140 | @Override
141 | public void onOpenFailure(Exception ex) {
142 | waiter.fail(ex.getMessage());
143 | }
144 |
145 | @Override
146 | public void onFailure(Exception ex) {
147 | waiter.fail(ex.getMessage());
148 | }
149 |
150 | @Override
151 | public void onClosed(String reason) {
152 | waiter.fail(reason);
153 | }
154 | });
155 |
156 | waiter.await(10000);
157 | }
158 |
159 | String generateJWT(String clientID) {
160 | final long iat = System.currentTimeMillis() / 1000L;
161 | final long exp = iat + 10 * 60L; // the token expires in 10 minutes
162 |
163 | final Map permissionMap = new HashMap() {
164 | {
165 | put("^*.$", new ScaledronePermission(true, true));
166 | put("^main-room$", new ScaledronePermission(false, false));
167 | }
168 | };
169 |
170 | final JWTSigner signer = new JWTSigner(secret);
171 | final Map claims = new HashMap();
172 | claims.put("client", clientID);
173 | claims.put("exp", exp);
174 | claims.put("channel", channel);
175 | claims.put("permissions", permissionMap);
176 |
177 | return signer.sign(claims);
178 | }
179 |
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/java/src/test/java/com/scaledrone/lib/BaseTest.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import net.jodah.concurrentunit.Waiter;
4 | import org.junit.Test;
5 |
6 | public class BaseTest {
7 |
8 | final String channel = System.getenv("AUTHLESS_CHANNEL");
9 |
10 | @Test
11 | public void test() throws Exception {
12 | final Waiter waiter = new Waiter();
13 | final Scaledrone drone = new Scaledrone(channel);
14 | waiter.assertEquals(null, drone.getClientID());
15 | drone.connect(new Listener() {
16 | @Override
17 | public void onOpen() {
18 | waiter.assertNotNull(drone.getClientID());
19 | waiter.resume();
20 | }
21 |
22 | @Override
23 | public void onOpenFailure(Exception ex) {
24 | waiter.fail(ex.getMessage());
25 | }
26 |
27 | @Override
28 | public void onFailure(Exception ex) {
29 | waiter.fail(ex.getMessage());
30 | }
31 |
32 | @Override
33 | public void onClosed(String reason) {
34 | waiter.fail(reason);
35 | }
36 | });
37 |
38 | waiter.await(10000);
39 | }
40 | }
--------------------------------------------------------------------------------
/java/src/test/java/com/scaledrone/lib/CloseTest.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import net.jodah.concurrentunit.Waiter;
4 | import org.junit.Test;
5 |
6 | import java.util.Timer;
7 | import java.util.TimerTask;
8 |
9 | public class CloseTest {
10 |
11 | final String channel = System.getenv("AUTHLESS_CHANNEL");
12 |
13 | @Test
14 | public void test() throws Exception {
15 | final Waiter waiter = new Waiter();
16 | final Scaledrone drone = new Scaledrone(channel);
17 | waiter.assertEquals(null, drone.getClientID());
18 | drone.connect(new Listener() {
19 | @Override
20 | public void onOpen() {
21 | waiter.assertNotNull(drone.getClientID());
22 | drone.close();
23 | Timer t = new Timer();
24 | t.schedule(new TimerTask() {
25 | @Override
26 | public void run() {
27 | drone.publish("some-room", "some message");
28 | }
29 | }, 100);
30 | }
31 |
32 | @Override
33 | public void onOpenFailure(Exception ex) {
34 | waiter.fail(ex.getMessage());
35 | }
36 |
37 | @Override
38 | public void onFailure(Exception ex) {
39 | waiter.fail(ex.getMessage());
40 | }
41 |
42 | @Override
43 | public void onClosed(String reason) {
44 | waiter.resume();
45 | }
46 | });
47 |
48 | waiter.await(10000);
49 | }
50 | }
--------------------------------------------------------------------------------
/java/src/test/java/com/scaledrone/lib/HistoryTest.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import net.jodah.concurrentunit.Waiter;
4 | import org.junit.Test;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Arrays;
8 | import java.util.List;
9 |
10 | public class HistoryTest {
11 |
12 | final String channel = System.getenv("AUTHLESS_CHANNEL");
13 |
14 | @Test
15 | public void testHistory() throws Exception {
16 | final Waiter waiter = new Waiter();
17 | final Scaledrone drone = new Scaledrone(channel);
18 | final String roomName = "myHistoryRoom";
19 | final List messages = new ArrayList();
20 | messages.addAll(Arrays.asList("first", "second", "third"));
21 | drone.connect(new Listener() {
22 | @Override
23 | public void onOpen() {
24 | try {
25 | for (String message : messages) {
26 | drone.publish(roomName, message);
27 | Thread.sleep(100);
28 | }
29 | Room room = drone.subscribe(roomName, new RoomListener() {
30 | @Override
31 | public void onOpen(Room room) {
32 |
33 | }
34 |
35 | @Override
36 | public void onOpenFailure(Room room, Exception ex) {
37 | waiter.fail(ex.getMessage());
38 | }
39 |
40 | @Override
41 | public void onMessage(Room room, Message message) {
42 |
43 | }
44 | }, new SubscribeOptions(messages.size()));
45 | room.listenToHistoryEvents(new HistoryRoomListener() {
46 | @Override
47 | public void onHistoryMessage(Room room, Message message) {
48 | waiter.assertEquals(drone.getClientID(), message.getClientID());
49 | waiter.assertTrue(message.getID().length() > 0);
50 | waiter.assertTrue(message.getTimestamp() > 0);
51 | waiter.assertEquals(messages.get(0), message.getData().asText());
52 | messages.remove(0);
53 | if (messages.isEmpty()) {
54 | waiter.resume();
55 | }
56 | }
57 | });
58 | } catch (InterruptedException e) {
59 | waiter.fail(e.getMessage());
60 | }
61 | }
62 |
63 | @Override
64 | public void onOpenFailure(Exception ex) {
65 | waiter.fail(ex.getMessage());
66 | }
67 |
68 | @Override
69 | public void onFailure(Exception ex) {
70 | waiter.fail(ex.getMessage());
71 | }
72 |
73 | @Override
74 | public void onClosed(String reason) {
75 | waiter.fail(reason);
76 | }
77 | });
78 |
79 | waiter.await(10000);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/java/src/test/java/com/scaledrone/lib/MessagingTest.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import net.jodah.concurrentunit.Waiter;
6 | import org.junit.Test;
7 |
8 | public class MessagingTest {
9 |
10 | final String channel = System.getenv("AUTHLESS_CHANNEL");
11 |
12 | @Test
13 | public void stringMessage() throws Exception {
14 | final Waiter waiter = new Waiter();
15 | final Scaledrone drone = new Scaledrone(channel);
16 | drone.connect(new Listener() {
17 | @Override
18 | public void onOpen() {
19 | drone.subscribe("room1", new RoomListener() {
20 | @Override
21 | public void onOpen(Room room) {
22 | room.publish("Hello there");
23 | }
24 |
25 | @Override
26 | public void onOpenFailure(Room room, Exception ex) {
27 | waiter.fail(ex.getMessage());
28 | }
29 |
30 | @Override
31 | public void onMessage(Room room, Message message) {
32 | waiter.assertEquals("Hello there", message.getData().asText());
33 | waiter.assertEquals(drone.getClientID(), message.getClientID());
34 | waiter.resume();
35 | }
36 | });
37 | }
38 |
39 | @Override
40 | public void onOpenFailure(Exception ex) {
41 | waiter.fail(ex.getMessage());
42 | }
43 |
44 | @Override
45 | public void onFailure(Exception ex) {
46 | waiter.fail(ex.getMessage());
47 | }
48 |
49 | @Override
50 | public void onClosed(String reason) {
51 | waiter.fail(reason);
52 | }
53 | });
54 |
55 | waiter.await(10000);
56 | }
57 |
58 | @Test
59 | public void objectMessage() throws Exception {
60 | final Waiter waiter = new Waiter();
61 | final Data data = new Data("bob", "red");
62 | final Scaledrone drone = new Scaledrone(channel);
63 | drone.connect(new Listener() {
64 | @Override
65 | public void onOpen() {
66 | drone.subscribe("room2", new RoomListener() {
67 | @Override
68 | public void onOpen(Room room) {
69 | room.publish(data);
70 | }
71 |
72 | @Override
73 | public void onOpenFailure(Room room, Exception ex) {
74 | waiter.fail(ex.getMessage());
75 | }
76 |
77 | @Override
78 | public void onMessage(Room room, Message message) {
79 | ObjectMapper mapper = new ObjectMapper();
80 | try {
81 | waiter.assertEquals(data, mapper.treeToValue(message.getData(), Data.class));
82 | waiter.resume();
83 | } catch (JsonProcessingException e) {
84 | e.printStackTrace();
85 | }
86 | }
87 | });
88 | }
89 |
90 | @Override
91 | public void onOpenFailure(Exception ex) {
92 | waiter.fail(ex.getMessage());
93 | }
94 |
95 | @Override
96 | public void onFailure(Exception ex) {
97 | waiter.fail(ex.getMessage());
98 | }
99 |
100 | @Override
101 | public void onClosed(String reason) {
102 | waiter.fail(reason);
103 | }
104 | });
105 |
106 | waiter.await(10000);
107 | }
108 | }
--------------------------------------------------------------------------------
/java/src/test/java/com/scaledrone/lib/ObservableRoomsTest.java:
--------------------------------------------------------------------------------
1 | package com.scaledrone.lib;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.JsonNode;
5 | import com.fasterxml.jackson.databind.ObjectMapper;
6 | import net.jodah.concurrentunit.Waiter;
7 | import org.junit.Test;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Objects;
11 |
12 | public class ObservableRoomsTest {
13 |
14 | private Scaledrone friend;
15 | final String channel = System.getenv("AUTHLESS_CHANNEL");
16 |
17 | @Test
18 | public void testPublish() throws Exception {
19 | final Waiter waiter = new Waiter();
20 |
21 | final Data data = new Data("Android", "#ff0000");
22 | final Scaledrone drone = new Scaledrone(channel, data);
23 | drone.connect(new Listener() {
24 | @Override
25 | public void onOpen() {
26 | drone.subscribe("observable-room1", new RoomListener() {
27 | @Override
28 | public void onOpen(Room room) {
29 | drone.publish("observable-room1", data);
30 | }
31 |
32 | @Override
33 | public void onOpenFailure(Room room, Exception ex) {
34 |
35 | }
36 |
37 | @Override
38 | public void onMessage(Room room, Message message) {
39 | waiter.assertEquals(drone.getClientID(), message.getMember().getId());
40 | ObjectMapper mapper = new ObjectMapper();
41 | try {
42 | Data d = mapper.treeToValue(message.getMember().getClientData(), Data.class);
43 | waiter.assertEquals(data, d);
44 | waiter.resume();
45 | } catch (JsonProcessingException e) {
46 | waiter.fail(e);
47 | }
48 | }
49 | });
50 | }
51 |
52 | @Override
53 | public void onOpenFailure(Exception ex) {
54 |
55 | }
56 |
57 | @Override
58 | public void onFailure(Exception ex) {
59 |
60 | }
61 |
62 | @Override
63 | public void onClosed(String reason) {
64 |
65 | }
66 | });
67 |
68 | waiter.await(10000, 1);
69 | }
70 |
71 | @Test
72 | public void testEvents() throws Exception {
73 | final Waiter waiter = new Waiter();
74 |
75 | final ObservableRoomListener silentListener = new ObservableRoomListener() {
76 |
77 | @Override
78 | public void onMembers(Room room, ArrayList members) {
79 |
80 | }
81 |
82 | @Override
83 | public void onMemberJoin(Room room, Member member) {
84 |
85 | }
86 |
87 | @Override
88 | public void onMemberLeave(Room room, Member member) {
89 |
90 | }
91 | };
92 |
93 | final Data data = new Data("Android", "#ff0000");
94 | connectNewScaledroneInstance(data, waiter, new ObservableRoomListener() {
95 | @Override
96 | public void onMembers(Room room, ArrayList members) {
97 | friend = connectNewScaledroneInstance(new Data("Java", "#00ff00"), waiter, silentListener);
98 |
99 | waiter.assertEquals(1, members.size());
100 | Member member = members.get(0);
101 | waiter.assertEquals(room.getScaledrone().getClientID(), member.getId());
102 | waiter.assertNotNull(member.getClientData());
103 | waiter.assertNull(member.getAuthData());
104 | ObjectMapper mapper = new ObjectMapper();
105 | try {
106 | Data d = mapper.treeToValue(member.getClientData(), Data.class);
107 | waiter.assertEquals(data, d);
108 | waiter.resume();
109 | } catch (JsonProcessingException e) {
110 | waiter.fail(e);
111 | }
112 | }
113 |
114 | @Override
115 | public void onMemberJoin(Room room, Member member) {
116 | friend.unsubscribe(room);
117 | waiter.assertTrue(room.getScaledrone().getClientID() != member.getId());
118 | waiter.assertNotNull(member.getClientData());
119 | waiter.assertNull(member.getAuthData());
120 | ObjectMapper mapper = new ObjectMapper();
121 | try {
122 | Data d = mapper.treeToValue(member.getClientData(), Data.class);
123 | waiter.assertEquals("Java", d.getName());
124 | waiter.assertEquals("#00ff00", d.getColor());
125 | waiter.resume();
126 | } catch (JsonProcessingException e) {
127 | waiter.fail(e);
128 | }
129 | }
130 |
131 | @Override
132 | public void onMemberLeave(Room room, Member member) {
133 | waiter.assertEquals(friend.getClientID(), member.getId());
134 | waiter.resume();
135 | }
136 | });
137 |
138 | waiter.await(10000, 3);
139 | }
140 |
141 | private Scaledrone connectNewScaledroneInstance(Data data, final Waiter waiter, final ObservableRoomListener listener) {
142 | final Scaledrone drone = new Scaledrone(channel, data);
143 | waiter.assertEquals(null, drone.getClientID());
144 | drone.connect(new Listener() {
145 | @Override
146 | public void onOpen() {
147 | waiter.assertNotNull(drone.getClientID());
148 | Room room = drone.subscribe("observable-room2", new RoomListener() {
149 | @Override
150 | public void onOpen(Room room) {
151 |
152 | }
153 |
154 | @Override
155 | public void onOpenFailure(Room room, Exception ex) {
156 | waiter.fail(ex.getMessage());
157 | }
158 |
159 | @Override
160 | public void onMessage(Room room, Message message) {
161 |
162 | }
163 | });
164 | room.listenToObservableEvents(listener);
165 | }
166 |
167 | @Override
168 | public void onOpenFailure(Exception ex) {
169 | waiter.fail(ex.getMessage());
170 | }
171 |
172 | @Override
173 | public void onFailure(Exception ex) {
174 | waiter.fail(ex.getMessage());
175 | }
176 |
177 | @Override
178 | public void onClosed(String reason) {
179 | waiter.fail(reason);
180 | }
181 | });
182 | return drone;
183 | }
184 | }
185 |
186 | class Data {
187 | private String name;
188 | private String color;
189 |
190 | public Data() {
191 |
192 | }
193 |
194 | public Data(String name, String color) {
195 | this.name = name;
196 | this.color = color;
197 | }
198 |
199 | public String getName() {
200 | return name;
201 | }
202 |
203 | public String getColor() {
204 | return color;
205 | }
206 |
207 | @Override
208 | public boolean equals(Object o) {
209 | if (this == o) return true;
210 | if (o == null || getClass() != o.getClass()) return false;
211 | Data data = (Data) o;
212 | return Objects.equals(name, data.name) && Objects.equals(color, data.color);
213 | }
214 |
215 | @Override
216 | public int hashCode() {
217 | return Objects.hash(name, color);
218 | }
219 |
220 | @Override
221 | public String toString() {
222 | return "Data{" +
223 | "name='" + name + '\'' +
224 | ", color='" + color + '\'' +
225 | '}';
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'scaledrone-java'
2 | include 'java'
3 |
4 | if(System.getenv('ANDROID_HOME')) {
5 | include 'android'
6 | }
--------------------------------------------------------------------------------