Skip to content

Commit 182d95d

Browse files
authored
Merge pull request #8 from sampie777/prevent_nullpointers_on_callbacks
Prevent end-user defined callbacks from blocking websocket code execution
2 parents 92a3ffb + 69a093e commit 182d95d

File tree

4 files changed

+200
-115
lines changed

4 files changed

+200
-115
lines changed

src/main/java/net/twasi/obsremotejava/OBSCommunicator.java

Lines changed: 134 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,11 @@ public void await() throws InterruptedException {
136136
public void onClose(int statusCode, String reason) {
137137
System.out.printf("Connection closed: %d - %s%n", statusCode, reason);
138138
this.closeLatch.countDown(); // trigger latch
139-
this.onDisconnect.run(null);
139+
try {
140+
this.onDisconnect.run(null);
141+
} catch (Throwable t) {
142+
t.printStackTrace();
143+
}
140144
}
141145

142146
@OnWebSocketConnect
@@ -169,98 +173,125 @@ public void onMessage(String msg) {
169173
Class type = messageTypes.get(responseBase.getMessageId());
170174
responseBase = (ResponseBase) new Gson().fromJson(msg, type);
171175

172-
switch (type.getSimpleName()) {
173-
case "GetVersionResponse":
174-
versionInfo = (GetVersionResponse) responseBase;
175-
System.out.printf("Connected to OBS. Websocket Version: %s, Studio Version: %s\n", versionInfo.getObsWebsocketVersion(), versionInfo.getObsStudioVersion());
176-
session.getRemote().sendStringByFuture(new Gson().toJson(new GetAuthRequiredRequest(this)));
177-
break;
178-
case "GetAuthRequiredResponse":
179-
GetAuthRequiredResponse authRequiredResponse = (GetAuthRequiredResponse) responseBase;
180-
if (authRequiredResponse.isAuthRequired()) {
181-
System.out.println("Authentication is required.");
182-
authenticateWithServer(authRequiredResponse.getChallenge(), authRequiredResponse.getSalt());
183-
} else {
184-
System.out.println("Authentication is not required. You're ready to go!");
185-
this.onConnect.run(versionInfo);
186-
}
187-
break;
188-
189-
case "AuthenticateResponse":
190-
AuthenticateResponse authenticateResponse = (AuthenticateResponse) responseBase;
191-
192-
if ("ok".equals(authenticateResponse.getStatus())) {
193-
this.onConnect.run(versionInfo);
194-
} else {
195-
this.onConnectionFailed.run("Failed to authenticate with password. Error: " + authenticateResponse.getError());
196-
}
197-
198-
break;
199-
default:
200-
if (callbacks.containsKey(type)) {
201-
callbacks.get(type).run(responseBase);
202-
} else {
203-
System.out.println("Invalid type received: " + type.getName());
204-
}
176+
try {
177+
processIncomingResponse(responseBase, type);
178+
} catch (Throwable t) {
179+
System.err.println("Failed to process response '" + type.getSimpleName() + "' from websocket.");
180+
t.printStackTrace();
205181
}
182+
206183
} else {
207184
JsonElement elem = new JsonParser().parse(msg);
208185
EventType eventType;
209186

210187
try {
211188
eventType = EventType.valueOf(elem.getAsJsonObject().get("update-type").getAsString());
212-
} catch (Exception e) {
189+
} catch (Throwable t) {
213190
return;
214191
}
215192

216-
switch (eventType) {
217-
case ReplayStarted:
218-
if (onReplayStarted != null)
219-
onReplayStarted.run(null);
220-
break;
221-
case ReplayStarting:
222-
if (onReplayStarting != null)
223-
onReplayStarting.run(null);
224-
break;
225-
case ReplayStopped:
226-
if (onReplayStopped != null)
227-
onReplayStopped.run(null);
228-
break;
229-
case ReplayStopping:
230-
if (onReplayStopping != null)
231-
onReplayStopping.run(null);
232-
break;
233-
case SwitchScenes:
234-
if (onSwitchScenes != null) {
235-
onSwitchScenes.run(new Gson().fromJson(msg, SwitchScenesResponse.class));
236-
}
237-
break;
238-
case ScenesChanged:
239-
if (onScenesChanged != null) {
240-
onScenesChanged.run(new Gson().fromJson(msg, ScenesChangedResponse.class));
241-
}
242-
break;
243-
case TransitionBegin:
244-
if (onTransitionBegin != null) {
245-
onTransitionBegin.run(new Gson().fromJson(msg, TransitionBeginResponse.class));
246-
}
247-
break;
248-
case TransitionEnd:
249-
if (onTransitionEnd != null) {
250-
onTransitionEnd.run(new Gson().fromJson(msg, TransitionEndResponse.class));
251-
}
252-
break;
193+
try {
194+
processIncomingEvent(msg, eventType);
195+
} catch (Throwable t) {
196+
System.err.println("Failed to execute callback for event: " + eventType);
197+
t.printStackTrace();
253198
}
254199
}
255-
} catch (Exception e) {
256-
System.out.println("Websocket Exception: " + e.getMessage());
200+
} catch (Throwable t) {
201+
System.err.println("Failed to process message from websocket.");
202+
t.printStackTrace();
203+
}
204+
}
205+
206+
private void processIncomingResponse(ResponseBase responseBase, Class type) {
207+
switch (type.getSimpleName()) {
208+
case "GetVersionResponse":
209+
versionInfo = (GetVersionResponse) responseBase;
210+
System.out.printf("Connected to OBS. Websocket Version: %s, Studio Version: %s\n", versionInfo.getObsWebsocketVersion(), versionInfo.getObsStudioVersion());
211+
session.getRemote().sendStringByFuture(new Gson().toJson(new GetAuthRequiredRequest(this)));
212+
break;
213+
214+
case "GetAuthRequiredResponse":
215+
GetAuthRequiredResponse authRequiredResponse = (GetAuthRequiredResponse) responseBase;
216+
if (authRequiredResponse.isAuthRequired()) {
217+
System.out.println("Authentication is required.");
218+
authenticateWithServer(authRequiredResponse.getChallenge(), authRequiredResponse.getSalt());
219+
} else {
220+
System.out.println("Authentication is not required. You're ready to go!");
221+
runOnConnect(versionInfo);
222+
}
223+
break;
224+
225+
case "AuthenticateResponse":
226+
AuthenticateResponse authenticateResponse = (AuthenticateResponse) responseBase;
227+
228+
if ("ok".equals(authenticateResponse.getStatus())) {
229+
runOnConnect(versionInfo);
230+
} else {
231+
runOnConnectionFailed("Failed to authenticate with password. Error: " + authenticateResponse.getError());
232+
}
233+
234+
break;
235+
default:
236+
if (!callbacks.containsKey(type)) {
237+
System.out.println("Invalid type received: " + type.getName());
238+
return;
239+
}
240+
241+
try {
242+
callbacks.get(type).run(responseBase);
243+
} catch (Throwable t) {
244+
System.err.println("Failed to execute callback for response: " + type);
245+
t.printStackTrace();
246+
}
247+
}
248+
}
249+
250+
private void processIncomingEvent(String msg, EventType eventType) {
251+
switch (eventType) {
252+
case ReplayStarted:
253+
if (onReplayStarted != null)
254+
onReplayStarted.run(null);
255+
break;
256+
case ReplayStarting:
257+
if (onReplayStarting != null)
258+
onReplayStarting.run(null);
259+
break;
260+
case ReplayStopped:
261+
if (onReplayStopped != null)
262+
onReplayStopped.run(null);
263+
break;
264+
case ReplayStopping:
265+
if (onReplayStopping != null)
266+
onReplayStopping.run(null);
267+
break;
268+
case SwitchScenes:
269+
if (onSwitchScenes != null) {
270+
onSwitchScenes.run(new Gson().fromJson(msg, SwitchScenesResponse.class));
271+
}
272+
break;
273+
case ScenesChanged:
274+
if (onScenesChanged != null) {
275+
onScenesChanged.run(new Gson().fromJson(msg, ScenesChangedResponse.class));
276+
}
277+
break;
278+
case TransitionBegin:
279+
if (onTransitionBegin != null) {
280+
onTransitionBegin.run(new Gson().fromJson(msg, TransitionBeginResponse.class));
281+
}
282+
break;
283+
case TransitionEnd:
284+
if (onTransitionEnd != null) {
285+
onTransitionEnd.run(new Gson().fromJson(msg, TransitionEndResponse.class));
286+
}
287+
break;
257288
}
258289
}
259290

260291
private void authenticateWithServer(String challenge, String salt) {
261292
if (password == null) {
262-
System.err.println("Authentication required by server but no password set for client");
263-
this.onConnectionFailed.run("Authentication required by server but no password set for client");
293+
System.err.println("Authentication required by server but no password set by client");
294+
runOnConnectionFailed("Authentication required by server but no password set by client");
264295
return;
265296
}
266297

@@ -270,7 +301,7 @@ private void authenticateWithServer(String challenge, String salt) {
270301
} catch (NoSuchAlgorithmException e) {
271302
System.err.println("Failed to perform password authentication with server");
272303
e.printStackTrace();
273-
this.onConnectionFailed.run("Failed to perform password authentication with server");
304+
runOnConnectionFailed("Failed to perform password authentication with server");
274305
return;
275306
}
276307

@@ -530,4 +561,30 @@ public void setStudioModeEnabled(boolean enabled, Callback callback){
530561
session.getRemote().sendStringByFuture(new Gson().toJson(request));
531562
callbacks.put(SetStudioModeEnabledResponse.class, callback);
532563
}
564+
565+
private void runOnConnectionFailed(String message) {
566+
if (onConnectionFailed == null) {
567+
return;
568+
}
569+
570+
try {
571+
onConnectionFailed.run(message);
572+
} catch (Throwable t) {
573+
System.err.println("Exception during callback execution for 'onConnectionFailed'");
574+
t.printStackTrace();
575+
}
576+
}
577+
578+
private void runOnConnect(GetVersionResponse versionInfo) {
579+
if (onConnect == null) {
580+
return;
581+
}
582+
583+
try {
584+
onConnect.run(versionInfo);
585+
} catch (Throwable t) {
586+
System.err.println("Exception during callback execution for 'onConnect'");
587+
t.printStackTrace();
588+
}
589+
}
533590
}

src/main/java/net/twasi/obsremotejava/OBSRemoteController.java

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public class OBSRemoteController {
1616
private OBSCommunicator communicator;
1717
private WebSocketClient client;
1818

19+
private StringCallback onConnectionFailed;
20+
1921
private boolean failed;
2022

2123
public OBSRemoteController(String address, boolean debug, String password) {
@@ -35,31 +37,17 @@ public OBSRemoteController(String address, boolean debug, String password) {
3537
failed = false;
3638
} catch (ExecutionException e) {
3739
if (e.getCause() instanceof ConnectException) {
38-
System.out.println("Connection to OBS failed.");
40+
System.out.println("Failed to connect to OBS.");
41+
e.printStackTrace();
42+
3943
failed = true;
40-
}
41-
}
4244

43-
/* new Thread() {
44-
@Override
45-
public void run() {
46-
try {
47-
communicator.await();
48-
} catch (InterruptedException e) {
49-
e.printStackTrace();
50-
}
45+
runOnConnectionFailed("Failed to connect to OBS");
5146
}
52-
}.start(); */
53-
//communicator.;
54-
47+
}
5548
} catch (Throwable t) {
5649
t.printStackTrace();
57-
} finally {
58-
try {
59-
//client.stop();
60-
} catch (Exception e) {
61-
e.printStackTrace();
62-
}
50+
runOnConnectionFailed("Failed to setup connection with OBS");
6351
}
6452
}
6553

@@ -73,7 +61,7 @@ public boolean isFailed() {
7361

7462
public void getScenes(Callback callback) {
7563
communicator.getScenes(callback);
76-
};
64+
}
7765

7866
public void registerConnectCallback(Callback onConnect) {
7967
communicator.registerOnConnect(onConnect);
@@ -84,6 +72,7 @@ public void registerDisconnectCallback(Callback onDisconnect) {
8472
}
8573

8674
public void registerConnectionFailedCallback(StringCallback onConnectionFailed) {
75+
this.onConnectionFailed = onConnectionFailed;
8776
communicator.registerOnConnectionFailed(onConnectionFailed);
8877
}
8978

@@ -244,4 +233,15 @@ public void saveReplayBuffer(Callback callback) {
244233
communicator.saveReplayBuffer(callback);
245234
}
246235

236+
private void runOnConnectionFailed(String message) {
237+
if (onConnectionFailed == null) {
238+
return;
239+
}
240+
241+
try {
242+
onConnectionFailed.run(message);
243+
} catch (Exception e) {
244+
e.printStackTrace();
245+
}
246+
}
247247
}

0 commit comments

Comments
 (0)