001package Torello.Browser; 002 003 004import javax.json.*; 005import java.io.*; 006 007import NeoVisionaries.WebSockets.*; 008 009import Torello.Java.*; 010import Torello.Java.Additional.*; 011 012import static Torello.Java.C.*; 013 014import java.util.concurrent.ConcurrentHashMap; 015import java.util.List; 016import java.util.Objects; 017import java.lang.reflect.Constructor; 018import java.util.function.Consumer; 019 020/** 021 * This class implements a connection to a Web-Browser using the Remote Debug Protocol over 022 * Web-Sockets. 023 * 024 * <H3 STYLE='background: black; color: white; padding: 0.5em;'>Browser Remote Debug Protocol 025 * Connection Class</H3> 026 * 027 * <BR />Java is capable of communicating with either a Headless instance of Google Chrome - <I>or 028 * any browser that implements the Remote Debuggin Protocol</I>. It is not mandatory to run the 029 * browser in headless mode, but it is more common. 030 */ 031@SuppressWarnings({"rawtypes", "unchecked"}) 032public class WebSocketSender implements Sender<String> 033{ 034 // ******************************************************************************************** 035 // ******************************************************************************************** 036 // Main Fields 037 // ******************************************************************************************** 038 // ******************************************************************************************** 039 040 041 // The Browser Connection 042 private final WebSocket webSocket; 043 044 // Stores the lists of promises 045 private ConcurrentHashMap<Integer, Promise<JsonObject, ? extends Object>> promises = 046 new ConcurrentHashMap<>(); 047 048 049 // ******************************************************************************************** 050 // ******************************************************************************************** 051 // User-Provided Handler Fields 052 // ******************************************************************************************** 053 // ******************************************************************************************** 054 055 056 final Consumer<BrowserEvent> eventHandler; 057 058 final Consumer<RDPError> rdpErrorHandler; 059 060 final Consumer<BrowserError> browserErrorHandler; 061 062 063 // ******************************************************************************************** 064 // ******************************************************************************************** 065 // Output Log Text Fields 066 // ******************************************************************************************** 067 // ******************************************************************************************** 068 069 070 private AppendableSafe raw = 071 new AppendableSafe(System.out, WebSocketSender::handleLogIOE); 072 073 private AppendableSafe app = 074 new AppendableSafe(System.out, WebSocketSender::handleLogIOE); 075 076 private AppendableSafe err = 077 new AppendableSafe(System.out, WebSocketSender::handleLogIOE); 078 079 AppendableSafe raw() { return raw; } 080 AppendableSafe app() { return app; } 081 AppendableSafe err() { return err; } 082 083 /** <EMBED CLASS='external-html' DATA-FILE-ID=WSS_RAW_LOG> */ 084 public AppendableSafe setRawFrameLog(Appendable a) 085 { return this.raw = new AppendableSafe(a, WebSocketSender::handleLogIOE); } 086 087 /** <EMBED CLASS='external-html' DATA-FILE-ID=WSS_APP_LOG> */ 088 public AppendableSafe setApplicationLayerLog(Appendable a) 089 { return this.app = new AppendableSafe(a, WebSocketSender::handleLogIOE); } 090 091 /** <EMBED CLASS='external-html' DATA-FILE-ID=WSS_ERR_LOG> */ 092 public AppendableSafe setErrorLog(Appendable a) 093 { return this.app = new AppendableSafe(a, WebSocketSender::handleLogIOE); } 094 095 private static void handleLogIOE(final IOException ioe) 096 { 097 System.err.println( 098 "There has been an exception throw while attempting to write to an output log.\n" + 099 "Cannot Proceed" 100 ); 101 102 throw new UnreachableError(); 103 } 104 105 106 // ******************************************************************************************** 107 // ******************************************************************************************** 108 // Constructor 109 // ******************************************************************************************** 110 // ******************************************************************************************** 111 112 113 /** 114 * Opens a Connection to a Web Browser using a Web-Socket. This class will now be 115 * ready to accept {@link #send(int, String, Promise)} messages to the browser. 116 * 117 * @param url This is a {@code URL} that is generated by the browser, and has a base 118 * {@code URL} that is just {@code 127.0.0.1}, followed by a <B STYLE='color:red'>port 119 * number</B>. There will also be an <B STYLE='color:red;'>identifier-code</B>. 120 * 121 * @throws IOException Throws if there are problems connecting the socket. 122 * 123 * @throws WebSocketException Throws if the NeoVisionaries Package encounters a problem 124 * building the socket connection. 125 */ 126 public WebSocketSender( 127 final String url, 128 final Consumer<BrowserEvent> eventHandler, 129 final Consumer<RDPError> rdpErrorHandler, 130 final Consumer<BrowserError> browserErrorHandler 131 ) 132 throws IOException, WebSocketException 133 { 134 Objects.requireNonNull(url, "Parameter 'url' has been passed null."); 135 Objects.requireNonNull(eventHandler, "Parameter 'eventHandler' has been passed null."); 136 137 this.rdpErrorHandler = (rdpErrorHandler == null) 138 ? RDPError.NO_OP 139 : rdpErrorHandler; 140 141 this.browserErrorHandler = (browserErrorHandler == null) 142 ? (BrowserError be) -> {} 143 : browserErrorHandler; 144 145 this.eventHandler = eventHandler; 146 147 final WebSocketListener webSocketListener = 148 new WSAdapter(this, this.promises); 149 150 this.webSocket = new WebSocketFactory() 151 .createSocket(url) 152 .addListener(webSocketListener) 153 .connect(); 154 155 System.out.println 156 (BYELLOW_BKGND + BBLACK + " Web Socket Connection Opened: " + RESET + url); 157 } 158 159 160 // ******************************************************************************************** 161 // ******************************************************************************************** 162 // Two Instance Methods 163 // ******************************************************************************************** 164 // ******************************************************************************************** 165 166 167 /** Closes the {@link WebSocket} connection to the Browser's Remote Debug Port. */ 168 public void disconnect() { webSocket.disconnect(); } 169 170 /** 171 * This method is the implementation-method for the {@link Sender} Functional-Interface. This 172 * message accepts a <B STYLE='color; red;'>Request & ID</B> pair, and then transmits that 173 * request to a Browser's Remote-Debugging Port over the {@code WebSocket}. It keeps the 174 * {@link Promise} that was created by the {@link Script} that sent this request, and saves 175 * that {@code Promise} until the Web-Socket receives a response about the request. 176 * 177 * @param requestID This may be any number. It is used to map requests sent over the Web 178 * Socket to responses received from it. 179 * 180 * @param requestJSON This is the JSON Method Request sent to the Browser 181 * 182 * @param promise This is a {@code Promise} which is automatically generated by the 183 * {@link Script} object that is sending the request. 184 */ 185 public void send(int requestID, String requestJSON, Promise promise) 186 { 187 this.promises.put(requestID, promise); 188 189 // Print the request-message that is about to be sent, and then send it. 190 app.append(BGREEN_BKGND + " Sending JSON: " + RESET + '\n' + requestJSON); 191 192 try 193 { webSocket.sendText(requestJSON); } 194 195 catch (Exception e) 196 { 197 throw new AsynchronousException( 198 "When attempting to send a JSON Request, an Exception was thrown:\n" + 199 e.getMessage() + "\nSee Exception getCause() for details.", e 200 ); 201 } 202 } 203}