Provide a means of exchanging messages between the JavaScript context of the

page hosting the applet and the launched application through a simple server
socket bound to the loopback address on any available port (where the port
is passed to the application as a system property).  Not tested yet, but it
shouldn't affect any deployments that don't specifically enable it.
This commit is contained in:
Andrzej Kapolka
2011-09-12 05:29:53 +00:00
parent 9611b988e0
commit a641c3c23d
4 changed files with 159 additions and 5 deletions
+6 -1
View File
@@ -23,12 +23,16 @@
<taskdef resource="org/apache/maven/artifact/ant/antlib.xml"
uri="urn:maven-artifact-ant" classpath="${maven-ant.jar}"/>
<artifact:pom id="pom" file="pom.xml"/>
<artifact:dependencies pathId="compile.classpath" pomRefId="pom" useScope="compile"/>
<artifact:dependencies pathId="pom-compile.classpath" pomRefId="pom" useScope="compile"/>
<artifact:dependencies pathId="testlibs.classpath" pomRefId="pom" useScope="test"/>
</target>
<target name="-prepare" depends="-init-maven-ant">
<mkdir dir="${deploy.dir}"/>
<path id="compile.classpath">
<path refid="pom-compile.classpath"/>
<pathelement location="${java.home}/lib/plugin.jar"/>
</path>
<path id="built.classpath">
<path refid="compile.classpath"/>
<pathelement location="${deploy.dir}/classes"/>
@@ -131,6 +135,7 @@
<outjar path="${deploy.dir}/getdown-pro${version.suff}.jar"/>
<libraryjar name="${rt.jar}"/>
<libraryjar name="${org.apache.ant:ant:jar}"/>
<libraryjar name="${java.home}/lib/plugin.jar"/>
<!-- keep the whole nine yards, just trim/obfuscate our depends -->
<keep name="com.threerings.getdown.**">
@@ -32,4 +32,8 @@ public class Properties
{
/** This property will be set to "true" on the application when it is being run by getdown. */
public static final String GETDOWN = "com.threerings.getdown";
/** If accepting connections from the launched application, this property
* will be set to the connection server port. */
public static final String CONNECT_PORT = "com.threerings.getdown.connectPort";
}
@@ -27,12 +27,17 @@ package com.threerings.getdown.launcher;
import java.awt.Container;
import java.awt.Image;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@@ -44,6 +49,11 @@ import java.security.cert.CertificateFactory;
import javax.swing.JApplet;
import javax.swing.JPanel;
import netscape.javascript.JSObject;
import com.threerings.getdown.data.Application;
import com.threerings.getdown.data.Properties;
import static com.threerings.getdown.Log.log;
/**
@@ -52,6 +62,35 @@ import static com.threerings.getdown.Log.log;
public class GetdownApplet extends JApplet
implements ImageLoader
{
/**
* Sets the JavaScript callback to invoke when a message is received from the launched app.
* The callback should be a function that accepts a single string parameter (the received
* message).
*/
public synchronized void setMessageCallback (JSObject callback)
{
_messageCallback = callback;
}
/**
* Attempts to send a message to the launched app.
*
* @return true if we succeeded in sending the message, false if the launched app has not (yet)
* established a connection to Getdown, or the send failed.
*/
public synchronized boolean sendMessage (String message)
{
if (_connectOut != null) {
try {
_connectOut.writeUTF(message);
return true;
} catch (IOException e) {
log.warning("Error sending message to app.", "message", message, e);
}
}
return false;
}
@Override // documentation inherited
public void init ()
{
@@ -108,6 +147,19 @@ public class GetdownApplet extends JApplet
return GetdownApplet.this;
}
@Override
protected void launch () {
// if so configured, create a server socket to listen
// for a connection from the app
if (_config.allowConnect) {
try {
startConnectServer();
} catch (IOException e) {
log.warning("Failed to start connect server.", e);
}
}
super.launch();
}
@Override
protected void exit (int exitCode) {
_app.releaseLock();
_config.redirect();
@@ -128,6 +180,62 @@ public class GetdownApplet extends JApplet
}
}
/**
* Attempts to start the server that will accept a connection from the launched app, allowing
* it to exchange messages with the JavaScript context.
*/
protected void startConnectServer ()
throws IOException
{
// bind and set a property with the local port that will be passed through to the app
_serverSocket = new ServerSocket(0, 0, InetAddress.getByName(null));
System.setProperty(Application.PROP_PASSTHROUGH_PREFIX + Properties.CONNECT_PORT,
String.valueOf(_serverSocket.getLocalPort()));
Thread thread = new Thread("ConnectServer") {
@Override
public void run () {
while (true) {
try {
acceptConnection();
} catch (IOException e) {
if (!_serverSocket.isClosed()) {
log.warning("Error accepting connection.", e);
}
break;
}
}
}
protected void acceptConnection () throws IOException {
Socket socket = _serverSocket.accept();
DataInputStream connectIn = new DataInputStream(socket.getInputStream());
synchronized (GetdownApplet.this) {
_connectOut = new DataOutputStream(socket.getOutputStream());
}
while (true) {
try {
String message = connectIn.readUTF();
synchronized (GetdownApplet.this) {
if (_messageCallback != null) {
_messageCallback.call("call",
new Object[] { _messageCallback, message });
}
}
} catch (IOException e) {
if (!socket.isClosed()) {
log.warning("Error reading message.", e);
}
break;
}
}
synchronized (GetdownApplet.this) {
_connectOut = null;
}
}
};
thread.setDaemon(true);
thread.start();
}
// implemented from ImageLoader
public Image loadImage (String path)
{
@@ -163,6 +271,25 @@ public class GetdownApplet extends JApplet
_getdown._app.releaseLock();
}
@Override // documentation inherited
public synchronized void destroy ()
{
if (_serverSocket != null) {
try {
_serverSocket.close();
} catch (IOException e) {
log.warning("Error closing server socket.", e);
}
}
if (_connectOut != null) {
try {
_connectOut.close();
} catch (IOException e) {
log.warning("Error closing connect socket/output stream.", e);
}
}
}
/**
* Creates the specified file and writes the supplied contents to it.
*/
@@ -207,4 +334,13 @@ public class GetdownApplet extends JApplet
/** An error encountered during initialization. */
protected String _errmsg;
/** The message callback registered by JavaScript on the containing page, if any. */
protected JSObject _messageCallback;
/** The server socket on which we listen for connections, if any. */
protected ServerSocket _serverSocket;
/** The output stream to the launched app, if a connection has been established. */
protected DataOutputStream _connectOut;
}
@@ -57,6 +57,7 @@ public class GetdownAppletConfig
public static final String ERRORBGIMAGE = "errorbgimage";
public static final String PARAM_DELIMITER = ".";
public static final String DIRECT = "direct";
public static final String CONNECT = "connect";
public static final String REDIRECT_ON_FINISH = "redirect_on_finish";
public static final String REDIRECT_ON_FINISH_TARGET = "redirect_on_finish_target";
@@ -107,6 +108,11 @@ public class GetdownAppletConfig
* separate java process (false). */
public boolean invokeDirect;
/** Indicates whether Getdown should allow the launched app to connect to it through a server
* socket bound to localhost on any available port in order to allow interaction with
* JavaScript code on the page containing the applet. */
public boolean allowConnect;
/** Optional default bounds for the status panel. */
public Rectangle statusBounds;
@@ -183,6 +189,9 @@ public class GetdownAppletConfig
String direct = getParameter(DIRECT, "false");
invokeDirect = Boolean.valueOf(direct);
String connect = getParameter(CONNECT, "false");
allowConnect = Boolean.valueOf(connect);
String redirectURL = getParameter(REDIRECT_ON_FINISH);
if (redirectURL != null) {
try {