001package Torello.Browser;
002
003import Torello.Java.Additional.AppendableSafe;
004import Torello.Java.UnreachableError;
005
006import java.io.IOException;
007import java.util.List;
008import java.util.Vector;
009import java.util.function.Consumer;
010
011
012/**
013 * Central record, log collector, and user-callback router for a CDP browser session.
014 *
015 * <BR /><BR />
016 * A {@code ConnRecord} is the single place where the Browser Tool stores the visible products of a
017 * Chrome DevTools Protocol connection.  A CDP session emits both ordinary text and strongly typed
018 * Java objects.  Some of that information comes from the low-level WebSocket driver, some comes
019 * from the Browser Tool's command/response dispatcher, and some comes from decoded browser events.
020 * Without a central record, the output quickly becomes scattered across the connection classes,
021 * handler classes, promises, and user examples.
022 *
023 * <BR /><BR />
024 * <EMBED CLASS='external-html' DATA-FILE-ID=ConnRecord>
025 */
026public class ConnRecord
027{
028    /**
029     * Creates an empty connection record.
030     *
031     * <BR /><BR />
032     * All object-saving and text-saving switches are enabled by default.  The public convenience
033     * references for browser/page connections and browser/page WebSocket senders are initialized
034     * to {@code null}, and may be filled in by example code or connection-building helpers.
035     */
036    public ConnRecord() { }
037
038
039    // ********************************************************************************************
040    // ********************************************************************************************
041    // User Convenience Reference Holders.
042    // ********************************************************************************************
043    // ********************************************************************************************
044
045
046    /**
047     * Optional reference to the browser-level connection descriptor used by this session.
048     *
049     * <BR /><BR /><DIV CLASS=JDHint>
050     * This field is provided strictly as a convenience holder for the example classes, and for
051     * user code.  Neither the methods in this class, nor others in this package read or modify it.
052     * 
053     * <BR /><BR />
054     * 🧠 This field will only be set by the programmer, and only for convenience, storage purposes
055     * </DIV>
056     */
057    public BrowserConn browserConn = null;
058
059    /**
060     * Optional reference to the page-level connection descriptor used by this session.
061     *
062     * <BR /><BR /><DIV CLASS=JDHint>
063     * This field is provided strictly as a convenience holder for the example classes, and for
064     * user code.  Neither the methods in this class, nor others in this package read or modify it.
065     * 
066     * <BR /><BR />
067     * 🧠 This field will only be set by the programmer, and only for convenience, storage purposes
068     * </DIV>
069     */
070    public PageConn pageConn = null;
071
072    /**
073     * Optional reference to the browser-level {@link WebSocketSender} used by this session.
074     *
075     * <BR /><BR /><DIV CLASS=JDHint>
076     * Example code often needs to keep both the browser socket and the page socket close at hand.
077     * This field provides a conventional place to store the browser socket.
078     * 
079     * <BR /><BR />
080     * 🧠 This field will only be set by the programmer, and only for convenience, storage purposes
081     * </DIV>
082     */
083    public WebSocketSender bws = null;
084
085    /**
086     * Optional reference to the page-level {@link WebSocketSender} used by this session.
087     *
088     * <BR /><BR /><DIV CLASS=JDHint>
089     * Example code often needs to keep both the browser socket and the page socket close at hand.
090     * This field provides a conventional place to store the page socket.
091     * 
092     * <BR /><BR />
093     * 🧠 This field will only be set by the programmer, and only for convenience, storage purposes
094     * </DIV>
095     */
096    public WebSocketSender pws = null;
097
098
099    // ********************************************************************************************
100    // ********************************************************************************************
101    // Events, Errors
102    // ********************************************************************************************
103    // ********************************************************************************************
104
105
106    /**
107     * When {@code true}, decoded {@link BrowserEvent} objects are saved to {@link #events}.
108     *
109     * <BR /><BR />
110     * Disabling this flag prevents the internal event list from growing, but it does not disable
111     * the user-provided event handler installed by {@link #setEventHandler(Consumer)}.
112     * 
113     * @see BrowserEvent
114     * @see #acceptEvent(BrowserEvent)
115     * @see #events
116     */
117    public boolean saveEvents = true;
118
119    /**
120     * When {@code true}, browser-returned command errors are saved to {@link #browserErrors}.
121     *
122     * <BR /><BR />
123     * A {@link BrowserError} represents an error reported by Chrome itself in response to a command
124     * that was sent through the CDP WebSocket connection.
125     * 
126     * @see BrowserError
127     * @see #acceptBrowserError(BrowserError)
128     * @see #browserErrors
129     */
130    public boolean saveBrowserErrors = true;
131
132    /**
133     * When {@code true}, internal Browser Tool / RDP errors are saved to {@link #rdpErrors}.
134     *
135     * <BR /><BR />
136     * An {@link RDPError} generally means that the Browser Tool could not parse, classify, load,
137     * decode, or otherwise handle a message that arrived from the Remote Debugging Protocol layer.
138     * 
139     * @see RDPError
140     * @see #acceptRDPError(RDPError)
141     * @see #rdpErrors
142     */
143    public boolean saveRDPErrors = true;
144
145    // Receives decoded browser events after they have been accepted by this record.
146    private Consumer<? super BrowserEvent<?>> userProvidedEventHandler = null;
147
148    // Receives browser-returned command errors after they have been accepted by this record.
149    private Consumer<? super BrowserError> userProvidedBrowserErrorHandler = null;
150
151    // Receives internal Browser Tool / RDP errors after they have been accepted by this record.
152    private Consumer<? super RDPError> userProvidedRDPErrorHandler = null;
153
154    /**
155     * Installs a callback that receives each decoded browser event.
156     *
157     * <BR /><BR />
158     * Passing {@code null} clears the current callback.  The callback is invoked even when
159     * {@link #saveEvents} is {@code false}; the save flag controls only whether the event is stored
160     * in {@link #events}.
161     * 
162     * <BR /><BR /><DIV CLASS=JDHint>
163     * If this handler is non-null, this is invoked <B STYLE='color:red;'>in addition to</B> the
164     * handler used to store and save {@code BrowserEvent} objects in a storage list.  This method 
165     * assigns the programmer's handler to the internal {@code 'userProvidedEventHandler'} field, 
166     * which is a <B STYLE='color:red;'>second</B> internal handler for events!
167     * </DIV>
168     *
169     * @param handler A callback for decoded browser events, or {@code null} to clear it.
170     */
171    public synchronized void setEventHandler(final Consumer<? super BrowserEvent<?>> handler)
172    { this.userProvidedEventHandler = handler; }
173
174    /**
175     * Installs a callback that receives browser-returned command errors.
176     *
177     * <BR /><BR />
178     * Passing {@code null} clears the current callback.  The callback is invoked even when
179     * {@link #saveBrowserErrors} is {@code false}; the save flag controls only whether the error is
180     * stored in {@link #browserErrors}.
181     *
182     * <BR /><BR /><DIV CLASS=JDHint>
183     * If this handler is non-null, this is invoked <B STYLE='color:red;'>in addition to</B> the
184     * handler used to store and save {@code BrowserError} objects in a storage list.  This assigns
185     * the programmer's handler to the internal {@code 'userProvidedBrowserErrorHandler'} field, 
186     * which is a <B STYLE='color:red;'>second</B> internal handler for browser errors!
187     * </DIV>
188     *
189     * @param handler A callback for browser-returned command errors, or {@code null} to clear it.
190     */
191    public synchronized void setBrowserErrorHandler(final Consumer<? super BrowserError> handler)
192    { this.userProvidedBrowserErrorHandler = handler; }
193
194    /**
195     * Installs a callback that receives internal Browser Tool / RDP errors.
196     *
197     * <BR /><BR />
198     * Passing {@code null} clears the current callback.  The callback is invoked even when
199     * {@link #saveRDPErrors} is {@code false}; the save flag controls only whether the error is
200     * stored in {@link #rdpErrors}.
201     *
202     * <BR /><BR /><DIV CLASS=JDHint>
203     * If this handler is non-null, this is invoked <B STYLE='color:red;'>in addition to</B> the
204     * handler used to store and save {@code RDPError} objects in a storage list.  This assigns the
205     * programmer's handler to the internal {@code 'userProvidedRDPErrorHandler'} field, which is a
206     * <B STYLE='color:red;'>second</B> internal handler for transport layer errors.
207     * </DIV>
208     *
209     * @param handler A callback for internal RDP errors, or {@code null} to clear it.
210     */
211    public synchronized void setRDPErrorHandler(final Consumer<? super RDPError> handler)
212    { this.userProvidedRDPErrorHandler = handler; }
213
214    /**
215     * List of decoded browser events received during this session.
216     *
217     * <BR /><BR /><DIV CLASS=JDHint>
218     * 📌 Events are added by the package-private event-handling pipeline after a raw CDP event
219     * message has been converted into a {@link BrowserEvent} instance.
220     * </DIV>
221     * 
222     * @see BrowserEvent
223     * @see #saveEvents
224     */
225    public final List<BrowserEvent<?>> events = new Vector<>();
226
227    /**
228     * List of browser-returned command errors received during this session.
229     *
230     * <BR /><BR /><DIV CLASS=JDHint>
231     * 📌 These errors are emitted by Chrome in response to a specific command request.  They are
232     * distinct from {@link #rdpErrors}, which are produced by the Java-side Browser Tool when it
233     * cannot correctly handle protocol traffic.
234     * </DIV>
235     * 
236     * @see BrowserError
237     * @see #saveBrowserErrors
238     */
239    public final List<BrowserError> browserErrors = new Vector<>();
240
241    /**
242     * List of internal Browser Tool / Remote Debugging Protocol errors received during this
243     * session.
244     *
245     * <BR /><BR /><DIV CLASS=JDHint>
246     * 📌 These errors usually indicate malformed, unexpected, unrecognized, or unhandled protocol
247     * traffic, or an exception thrown while the Java-side dispatch logic is decoding browser data.
248     * </DIV>
249     * 
250     * @see RDPError
251     * @see #saveRDPErrors
252     */
253    public final List<RDPError> rdpErrors = new Vector<>();
254
255    /**
256     * Accepts a decoded browser event and routes it to storage and/or the user callback.
257     *
258     * <BR /><BR />
259     * This method is protected because it is intended for the Browser Tool's internal handler
260     * classes, not for direct end-user invocation.
261     *
262     * @param be The decoded browser event that has been received from Chrome.
263     */
264    protected synchronized void acceptEvent(BrowserEvent<?> be)
265    {
266        if (this.saveEvents)
267            this.events.add(be);
268
269        if (this.userProvidedEventHandler != null)
270            this.userProvidedEventHandler.accept(be);
271    }
272
273    /**
274     * Accepts a browser-returned command error and routes it to storage and/or the user callback.
275     *
276     * <BR /><BR />
277     * This method is protected because it is intended for the Browser Tool's internal command
278     * response handler, not for direct end-user invocation.
279     *
280     * @param bErr The browser-returned command error that has been received from Chrome.
281     */
282    protected synchronized void acceptBrowserError(BrowserError bErr)
283    {
284        if (this.saveBrowserErrors)
285            this.browserErrors.add(bErr);
286
287        if (this.userProvidedBrowserErrorHandler != null)
288            this.userProvidedBrowserErrorHandler.accept(bErr);
289    }
290
291    /**
292     * Accepts an internal Browser Tool / RDP error and routes it to storage and/or the user
293     * callback.
294     *
295     * <BR /><BR />
296     * This method is protected because it is intended for the Browser Tool's internal
297     * WebSocket and dispatch handlers, not for direct end-user invocation.
298     *
299     * @param rdpErr The internal RDP error that has been produced by the Browser Tool.
300     */
301    protected synchronized void acceptRDPError(RDPError rdpErr)
302    {
303        if (this.saveRDPErrors)
304            this.rdpErrors.add(rdpErr);
305
306        if (this.userProvidedRDPErrorHandler != null)
307            this.userProvidedRDPErrorHandler.accept(rdpErr);
308    }
309
310
311    // ********************************************************************************************
312    // ********************************************************************************************
313    // Output Log Text Fields
314    // ********************************************************************************************
315    // ********************************************************************************************
316
317
318    /**
319     * Saved raw WebSocket / NeoVisionaries transport text.
320     *
321     * <BR /><BR />
322     * This stream receives low-level socket-frame, thread, ping, pong, close, disconnect, and other
323     * driver-layer messages.  It is the closest stream to the WebSocket transport itself.
324     * 
325     * @see #saveRawText
326     */
327    public final StringBuffer rawSB = new StringBuffer();
328
329    /**
330     * Saved application-level CDP interpretation text.
331     *
332     * <BR /><BR />
333     * This stream receives text describing outgoing JSON requests, incoming JSON datagrams,
334     * command-response handling, browser-event decoding, and promise cancellation.
335     * 
336     * @see #saveAppText
337     */
338    public final StringBuffer appSB = new StringBuffer();
339
340    /**
341     * Saved error and diagnostic text.
342     *
343     * <BR /><BR />
344     * This stream receives text describing browser-command errors, unrecognized protocol messages,
345     * WebSocket exceptions, malformed responses, and other failures detected while handling CDP
346     * traffic.
347     * 
348     * @see #saveErrText
349     */
350    public final StringBuffer errSB = new StringBuffer();
351
352    /**
353     * When {@code true}, text written through {@link #raw(String)} is saved to {@link #rawSB}.
354     *
355     * <BR /><BR />
356     * Disabling this flag prevents the raw-text buffer from growing, but does not disable the
357     * user-provided raw-text receiver installed by {@link #setRawTextReceiver(Appendable)}.
358     * 
359     * @see #rawSB
360     */
361    public boolean saveRawText = true;
362
363    /**
364     * When {@code true}, text written through {@link #app(String)} is saved to {@link #appSB}.
365     *
366     * <BR /><BR />
367     * Disabling this flag prevents the application-text buffer from growing, but does not disable
368     * the user-provided application-text receiver installed by
369     * {@link #setAppTextReceiver(Appendable)}.
370     * 
371     * @see #appSB
372     */
373    public boolean saveAppText = true;
374
375    /**
376     * When {@code true}, text written through {@link #err(String)} is saved to {@link #errSB}.
377     *
378     * <BR /><BR />
379     * Disabling this flag prevents the error-text buffer from growing, but does not disable the
380     * user-provided error-text receiver installed by {@link #setErrTextReceiver(Appendable)}.
381     * 
382     * @see #errSB
383     */
384    public boolean saveErrText = true;
385
386    /** Receives raw WebSocket text after it has been accepted by this record. */
387    private AppendableSafe userProvidedRaw = null;
388
389    /** Receives application-level CDP text after it has been accepted by this record. */
390    private AppendableSafe userProvidedApp = null;
391
392    /** Receives error and diagnostic text after it has been accepted by this record. */
393    private AppendableSafe userProvidedErr = null;
394
395    /**
396     * Installs an {@link Appendable} that receives raw WebSocket / transport text.
397     * 
398     * <BR /><BR />
399     * Typical values are {@code System.out}, a {@code StringBuilder}, a {@code Writer}, or an
400     * {@link AppendableSafe}.  Passing {@code null} clears the current receiver.
401     *
402     * @param a The destination for raw WebSocket text, or {@code null} to clear it.
403     * 
404     * @see AppendableSafe
405     * @see #checkIsInstance(Appendable)
406     */
407    public synchronized void setRawTextReceiver(final Appendable a)
408    { this.userProvidedRaw = checkIsInstance(a); }
409
410    /**
411     * Installs an {@link Appendable} that receives application-level CDP interpretation text.
412     *
413     * <BR /><BR />
414     * Typical values are {@code System.out}, a {@code StringBuilder}, a {@code Writer}, or an
415     * {@link AppendableSafe}.  Passing {@code null} clears the current receiver.
416     *
417     * @param a The destination for application-level CDP text, or {@code null} to clear it.
418     * 
419     * @see AppendableSafe
420     * @see #checkIsInstance(Appendable)
421     */
422    public synchronized void setAppTextReceiver(final Appendable a)
423    { this.userProvidedApp = checkIsInstance(a); }
424
425    /**
426     * Installs an {@link Appendable} that receives error and diagnostic text.
427     *
428     * <BR /><BR />
429     * Typical values are {@code System.out}, a {@code StringBuilder}, a {@code Writer}, or an
430     * {@link AppendableSafe}.  Passing {@code null} clears the current receiver.
431     *
432     * @param a The destination for error and diagnostic text, or {@code null} to clear it.
433     * 
434     * @see AppendableSafe
435     * @see #checkIsInstance(Appendable)
436     */
437    public synchronized void setErrTextReceiver(final Appendable a)
438    { this.userProvidedErr = checkIsInstance(a); }
439
440    /**
441     * Accepts raw WebSocket / transport text and routes it to storage and/or the raw receiver.
442     *
443     * <BR /><BR />
444     * This method is protected because raw transport text is produced by the internal
445     * WebSocket adapter, not by ordinary end-user code.
446     *
447     * @param text The raw WebSocket / transport text to record.
448     * 
449     * @see #saveRawText
450     * @see #rawSB
451     */
452    protected synchronized void raw(final String text)
453    {
454        if (this.saveRawText)               this.rawSB.append(text);
455        if (this.userProvidedRaw != null)   this.userProvidedRaw.append(text);
456    }
457
458    /**
459     * Accepts application-level CDP text and routes it to storage and/or the application receiver.
460     *
461     * <BR /><BR />
462     * This method is protected because application-level CDP text is produced by the internal
463     * sender, dispatcher, event handler, and command-response handler.
464     *
465     * @param text The application-level CDP text to record.
466     * 
467     * @see #saveAppText
468     * @see #appSB
469     */
470    protected synchronized void app(final String text)
471    {
472        if (this.saveAppText)               this.appSB.append(text);
473        if (this.userProvidedApp != null)   this.userProvidedApp.append(text);
474    }
475
476    /**
477     * Accepts error and diagnostic text and routes it to storage and/or the error receiver.
478     *
479     * <BR /><BR />
480     * This method is protected because error text is produced by the internal WebSocket,
481     * dispatch, event, and command-response handlers.
482     *
483     * @param text The error or diagnostic text to record.
484     * 
485     * @see #saveErrText
486     * @see #errSB
487     */
488    protected synchronized void err(final String text)
489    {
490        if (this.saveErrText)               this.errSB.append(text);
491        if (this.userProvidedErr != null)   this.userProvidedErr.append(text);
492    }
493
494    /**
495     * Ensures that a user-provided {@link Appendable} is safe for unchecked logging calls.
496     *
497     * <BR /><BR />
498     * If the appendable is already an {@link AppendableSafe}, it is returned directly.  Otherwise,
499     * it is wrapped so that {@link IOException} may be handled by
500     * {@link #handleLogIOE(IOException)}.
501     * A {@code null} value is preserved as {@code null}.
502     *
503     * @param a The appendable to check or wrap.
504     * @return An {@link AppendableSafe} wrapper, the original wrapper, or {@code null}.
505     */
506    protected static AppendableSafe checkIsInstance(final Appendable a)
507    {
508        return (a == null)
509            ? null
510            : (a instanceof AppendableSafe)
511                ? (AppendableSafe) a
512                : new AppendableSafe(a, ConnRecord::handleLogIOE);
513    }
514
515    /**
516     * Handles an {@link IOException} thrown while writing diagnostic text.
517     *
518     * <BR /><BR />
519     * Logging failures are treated as unreachable Browser Tool failures.  The Browser Tool expects
520     * its own diagnostic stream plumbing to be reliable once it has been installed.
521     *
522     * @param ioe The exception thrown by the underlying appendable.
523     */
524    private static void handleLogIOE(final IOException ioe)
525    {
526        System.err.println(
527            "There has been an exception throw while attempting to write to an output log.\n" +
528            "Cannot Proceed"
529        );
530
531        throw new UnreachableError(ioe);
532    }
533
534}