1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
package Torello.Browser;

import Torello.Java.Additional.AppendableSafe;
import Torello.Java.UnreachableError;

import java.io.IOException;
import java.util.List;
import java.util.Vector;
import java.util.function.Consumer;


/**
 * Central record, log collector, and user-callback router for a CDP browser session.
 *
 * <BR /><BR />
 * A {@code ConnRecord} is the single place where the Browser Tool stores the visible products of a
 * Chrome DevTools Protocol connection.  A CDP session emits both ordinary text and strongly typed
 * Java objects.  Some of that information comes from the low-level WebSocket driver, some comes
 * from the Browser Tool's command/response dispatcher, and some comes from decoded browser events.
 * Without a central record, the output quickly becomes scattered across the connection classes,
 * handler classes, promises, and user examples.
 *
 * <BR /><BR />
 * <EMBED CLASS='external-html' DATA-FILE-ID=ConnRecord>
 */
public class ConnRecord
{
    /**
     * Creates an empty connection record.
     *
     * <BR /><BR />
     * All object-saving and text-saving switches are enabled by default.  The public convenience
     * references for browser/page connections and browser/page WebSocket senders are initialized
     * to {@code null}, and may be filled in by example code or connection-building helpers.
     */
    public ConnRecord() { }


    // ********************************************************************************************
    // ********************************************************************************************
    // User Convenience Reference Holders.
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Optional reference to the browser-level connection descriptor used by this session.
     *
     * <BR /><BR /><DIV CLASS=JDHint>
     * This field is provided strictly as a convenience holder for the example classes, and for
     * user code.  Neither the methods in this class, nor others in this package read or modify it.
     * 
     * <BR /><BR />
     * 🧠 This field will only be set by the programmer, and only for convenience, storage purposes
     * </DIV>
     */
    public BrowserConn browserConn = null;

    /**
     * Optional reference to the page-level connection descriptor used by this session.
     *
     * <BR /><BR /><DIV CLASS=JDHint>
     * This field is provided strictly as a convenience holder for the example classes, and for
     * user code.  Neither the methods in this class, nor others in this package read or modify it.
     * 
     * <BR /><BR />
     * 🧠 This field will only be set by the programmer, and only for convenience, storage purposes
     * </DIV>
     */
    public PageConn pageConn = null;

    /**
     * Optional reference to the browser-level {@link WebSocketSender} used by this session.
     *
     * <BR /><BR /><DIV CLASS=JDHint>
     * Example code often needs to keep both the browser socket and the page socket close at hand.
     * This field provides a conventional place to store the browser socket.
     * 
     * <BR /><BR />
     * 🧠 This field will only be set by the programmer, and only for convenience, storage purposes
     * </DIV>
     */
    public WebSocketSender bws = null;

    /**
     * Optional reference to the page-level {@link WebSocketSender} used by this session.
     *
     * <BR /><BR /><DIV CLASS=JDHint>
     * Example code often needs to keep both the browser socket and the page socket close at hand.
     * This field provides a conventional place to store the page socket.
     * 
     * <BR /><BR />
     * 🧠 This field will only be set by the programmer, and only for convenience, storage purposes
     * </DIV>
     */
    public WebSocketSender pws = null;


    // ********************************************************************************************
    // ********************************************************************************************
    // Events, Errors
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * When {@code true}, decoded {@link BrowserEvent} objects are saved to {@link #events}.
     *
     * <BR /><BR />
     * Disabling this flag prevents the internal event list from growing, but it does not disable
     * the user-provided event handler installed by {@link #setEventHandler(Consumer)}.
     * 
     * @see BrowserEvent
     * @see #acceptEvent(BrowserEvent)
     * @see #events
     */
    public boolean saveEvents = true;

    /**
     * When {@code true}, browser-returned command errors are saved to {@link #browserErrors}.
     *
     * <BR /><BR />
     * A {@link BrowserError} represents an error reported by Chrome itself in response to a command
     * that was sent through the CDP WebSocket connection.
     * 
     * @see BrowserError
     * @see #acceptBrowserError(BrowserError)
     * @see #browserErrors
     */
    public boolean saveBrowserErrors = true;

    /**
     * When {@code true}, internal Browser Tool / RDP errors are saved to {@link #rdpErrors}.
     *
     * <BR /><BR />
     * An {@link RDPError} generally means that the Browser Tool could not parse, classify, load,
     * decode, or otherwise handle a message that arrived from the Remote Debugging Protocol layer.
     * 
     * @see RDPError
     * @see #acceptRDPError(RDPError)
     * @see #rdpErrors
     */
    public boolean saveRDPErrors = true;

    // Receives decoded browser events after they have been accepted by this record.
    private Consumer<? super BrowserEvent<?>> userProvidedEventHandler = null;

    // Receives browser-returned command errors after they have been accepted by this record.
    private Consumer<? super BrowserError> userProvidedBrowserErrorHandler = null;

    // Receives internal Browser Tool / RDP errors after they have been accepted by this record.
    private Consumer<? super RDPError> userProvidedRDPErrorHandler = null;

    /**
     * Installs a callback that receives each decoded browser event.
     *
     * <BR /><BR />
     * Passing {@code null} clears the current callback.  The callback is invoked even when
     * {@link #saveEvents} is {@code false}; the save flag controls only whether the event is stored
     * in {@link #events}.
     * 
     * <BR /><BR /><DIV CLASS=JDHint>
     * If this handler is non-null, this is invoked <B STYLE='color:red;'>in addition to</B> the
     * handler used to store and save {@code BrowserEvent} objects in a storage list.  This method 
     * assigns the programmer's handler to the internal {@code 'userProvidedEventHandler'} field, 
     * which is a <B STYLE='color:red;'>second</B> internal handler for events!
     * </DIV>
     *
     * @param handler A callback for decoded browser events, or {@code null} to clear it.
     */
    public synchronized void setEventHandler(final Consumer<? super BrowserEvent<?>> handler)
    { this.userProvidedEventHandler = handler; }

    /**
     * Installs a callback that receives browser-returned command errors.
     *
     * <BR /><BR />
     * Passing {@code null} clears the current callback.  The callback is invoked even when
     * {@link #saveBrowserErrors} is {@code false}; the save flag controls only whether the error is
     * stored in {@link #browserErrors}.
     *
     * <BR /><BR /><DIV CLASS=JDHint>
     * If this handler is non-null, this is invoked <B STYLE='color:red;'>in addition to</B> the
     * handler used to store and save {@code BrowserError} objects in a storage list.  This assigns
     * the programmer's handler to the internal {@code 'userProvidedBrowserErrorHandler'} field, 
     * which is a <B STYLE='color:red;'>second</B> internal handler for browser errors!
     * </DIV>
     *
     * @param handler A callback for browser-returned command errors, or {@code null} to clear it.
     */
    public synchronized void setBrowserErrorHandler(final Consumer<? super BrowserError> handler)
    { this.userProvidedBrowserErrorHandler = handler; }

    /**
     * Installs a callback that receives internal Browser Tool / RDP errors.
     *
     * <BR /><BR />
     * Passing {@code null} clears the current callback.  The callback is invoked even when
     * {@link #saveRDPErrors} is {@code false}; the save flag controls only whether the error is
     * stored in {@link #rdpErrors}.
     *
     * <BR /><BR /><DIV CLASS=JDHint>
     * If this handler is non-null, this is invoked <B STYLE='color:red;'>in addition to</B> the
     * handler used to store and save {@code RDPError} objects in a storage list.  This assigns the
     * programmer's handler to the internal {@code 'userProvidedRDPErrorHandler'} field, which is a
     * <B STYLE='color:red;'>second</B> internal handler for transport layer errors.
     * </DIV>
     *
     * @param handler A callback for internal RDP errors, or {@code null} to clear it.
     */
    public synchronized void setRDPErrorHandler(final Consumer<? super RDPError> handler)
    { this.userProvidedRDPErrorHandler = handler; }

    /**
     * List of decoded browser events received during this session.
     *
     * <BR /><BR /><DIV CLASS=JDHint>
     * 📌 Events are added by the package-private event-handling pipeline after a raw CDP event
     * message has been converted into a {@link BrowserEvent} instance.
     * </DIV>
     * 
     * @see BrowserEvent
     * @see #saveEvents
     */
    public final List<BrowserEvent<?>> events = new Vector<>();

    /**
     * List of browser-returned command errors received during this session.
     *
     * <BR /><BR /><DIV CLASS=JDHint>
     * 📌 These errors are emitted by Chrome in response to a specific command request.  They are
     * distinct from {@link #rdpErrors}, which are produced by the Java-side Browser Tool when it
     * cannot correctly handle protocol traffic.
     * </DIV>
     * 
     * @see BrowserError
     * @see #saveBrowserErrors
     */
    public final List<BrowserError> browserErrors = new Vector<>();

    /**
     * List of internal Browser Tool / Remote Debugging Protocol errors received during this
     * session.
     *
     * <BR /><BR /><DIV CLASS=JDHint>
     * 📌 These errors usually indicate malformed, unexpected, unrecognized, or unhandled protocol
     * traffic, or an exception thrown while the Java-side dispatch logic is decoding browser data.
     * </DIV>
     * 
     * @see RDPError
     * @see #saveRDPErrors
     */
    public final List<RDPError> rdpErrors = new Vector<>();

    /**
     * Accepts a decoded browser event and routes it to storage and/or the user callback.
     *
     * <BR /><BR />
     * This method is protected because it is intended for the Browser Tool's internal handler
     * classes, not for direct end-user invocation.
     *
     * @param be The decoded browser event that has been received from Chrome.
     */
    protected synchronized void acceptEvent(BrowserEvent<?> be)
    {
        if (this.saveEvents)
            this.events.add(be);

        if (this.userProvidedEventHandler != null)
            this.userProvidedEventHandler.accept(be);
    }

    /**
     * Accepts a browser-returned command error and routes it to storage and/or the user callback.
     *
     * <BR /><BR />
     * This method is protected because it is intended for the Browser Tool's internal command
     * response handler, not for direct end-user invocation.
     *
     * @param bErr The browser-returned command error that has been received from Chrome.
     */
    protected synchronized void acceptBrowserError(BrowserError bErr)
    {
        if (this.saveBrowserErrors)
            this.browserErrors.add(bErr);

        if (this.userProvidedBrowserErrorHandler != null)
            this.userProvidedBrowserErrorHandler.accept(bErr);
    }

    /**
     * Accepts an internal Browser Tool / RDP error and routes it to storage and/or the user
     * callback.
     *
     * <BR /><BR />
     * This method is protected because it is intended for the Browser Tool's internal
     * WebSocket and dispatch handlers, not for direct end-user invocation.
     *
     * @param rdpErr The internal RDP error that has been produced by the Browser Tool.
     */
    protected synchronized void acceptRDPError(RDPError rdpErr)
    {
        if (this.saveRDPErrors)
            this.rdpErrors.add(rdpErr);

        if (this.userProvidedRDPErrorHandler != null)
            this.userProvidedRDPErrorHandler.accept(rdpErr);
    }


    // ********************************************************************************************
    // ********************************************************************************************
    // Output Log Text Fields
    // ********************************************************************************************
    // ********************************************************************************************


    /**
     * Saved raw WebSocket / NeoVisionaries transport text.
     *
     * <BR /><BR />
     * This stream receives low-level socket-frame, thread, ping, pong, close, disconnect, and other
     * driver-layer messages.  It is the closest stream to the WebSocket transport itself.
     * 
     * @see #saveRawText
     */
    public final StringBuffer rawSB = new StringBuffer();

    /**
     * Saved application-level CDP interpretation text.
     *
     * <BR /><BR />
     * This stream receives text describing outgoing JSON requests, incoming JSON datagrams,
     * command-response handling, browser-event decoding, and promise cancellation.
     * 
     * @see #saveAppText
     */
    public final StringBuffer appSB = new StringBuffer();

    /**
     * Saved error and diagnostic text.
     *
     * <BR /><BR />
     * This stream receives text describing browser-command errors, unrecognized protocol messages,
     * WebSocket exceptions, malformed responses, and other failures detected while handling CDP
     * traffic.
     * 
     * @see #saveErrText
     */
    public final StringBuffer errSB = new StringBuffer();

    /**
     * When {@code true}, text written through {@link #raw(String)} is saved to {@link #rawSB}.
     *
     * <BR /><BR />
     * Disabling this flag prevents the raw-text buffer from growing, but does not disable the
     * user-provided raw-text receiver installed by {@link #setRawTextReceiver(Appendable)}.
     * 
     * @see #rawSB
     */
    public boolean saveRawText = true;

    /**
     * When {@code true}, text written through {@link #app(String)} is saved to {@link #appSB}.
     *
     * <BR /><BR />
     * Disabling this flag prevents the application-text buffer from growing, but does not disable
     * the user-provided application-text receiver installed by
     * {@link #setAppTextReceiver(Appendable)}.
     * 
     * @see #appSB
     */
    public boolean saveAppText = true;

    /**
     * When {@code true}, text written through {@link #err(String)} is saved to {@link #errSB}.
     *
     * <BR /><BR />
     * Disabling this flag prevents the error-text buffer from growing, but does not disable the
     * user-provided error-text receiver installed by {@link #setErrTextReceiver(Appendable)}.
     * 
     * @see #errSB
     */
    public boolean saveErrText = true;

    /** Receives raw WebSocket text after it has been accepted by this record. */
    private AppendableSafe userProvidedRaw = null;

    /** Receives application-level CDP text after it has been accepted by this record. */
    private AppendableSafe userProvidedApp = null;

    /** Receives error and diagnostic text after it has been accepted by this record. */
    private AppendableSafe userProvidedErr = null;

    /**
     * Installs an {@link Appendable} that receives raw WebSocket / transport text.
     * 
     * <BR /><BR />
     * Typical values are {@code System.out}, a {@code StringBuilder}, a {@code Writer}, or an
     * {@link AppendableSafe}.  Passing {@code null} clears the current receiver.
     *
     * @param a The destination for raw WebSocket text, or {@code null} to clear it.
     * 
     * @see AppendableSafe
     * @see #checkIsInstance(Appendable)
     */
    public synchronized void setRawTextReceiver(final Appendable a)
    { this.userProvidedRaw = checkIsInstance(a); }

    /**
     * Installs an {@link Appendable} that receives application-level CDP interpretation text.
     *
     * <BR /><BR />
     * Typical values are {@code System.out}, a {@code StringBuilder}, a {@code Writer}, or an
     * {@link AppendableSafe}.  Passing {@code null} clears the current receiver.
     *
     * @param a The destination for application-level CDP text, or {@code null} to clear it.
     * 
     * @see AppendableSafe
     * @see #checkIsInstance(Appendable)
     */
    public synchronized void setAppTextReceiver(final Appendable a)
    { this.userProvidedApp = checkIsInstance(a); }

    /**
     * Installs an {@link Appendable} that receives error and diagnostic text.
     *
     * <BR /><BR />
     * Typical values are {@code System.out}, a {@code StringBuilder}, a {@code Writer}, or an
     * {@link AppendableSafe}.  Passing {@code null} clears the current receiver.
     *
     * @param a The destination for error and diagnostic text, or {@code null} to clear it.
     * 
     * @see AppendableSafe
     * @see #checkIsInstance(Appendable)
     */
    public synchronized void setErrTextReceiver(final Appendable a)
    { this.userProvidedErr = checkIsInstance(a); }

    /**
     * Accepts raw WebSocket / transport text and routes it to storage and/or the raw receiver.
     *
     * <BR /><BR />
     * This method is protected because raw transport text is produced by the internal
     * WebSocket adapter, not by ordinary end-user code.
     *
     * @param text The raw WebSocket / transport text to record.
     * 
     * @see #saveRawText
     * @see #rawSB
     */
    protected synchronized void raw(final String text)
    {
        if (this.saveRawText)               this.rawSB.append(text);
        if (this.userProvidedRaw != null)   this.userProvidedRaw.append(text);
    }

    /**
     * Accepts application-level CDP text and routes it to storage and/or the application receiver.
     *
     * <BR /><BR />
     * This method is protected because application-level CDP text is produced by the internal
     * sender, dispatcher, event handler, and command-response handler.
     *
     * @param text The application-level CDP text to record.
     * 
     * @see #saveAppText
     * @see #appSB
     */
    protected synchronized void app(final String text)
    {
        if (this.saveAppText)               this.appSB.append(text);
        if (this.userProvidedApp != null)   this.userProvidedApp.append(text);
    }

    /**
     * Accepts error and diagnostic text and routes it to storage and/or the error receiver.
     *
     * <BR /><BR />
     * This method is protected because error text is produced by the internal WebSocket,
     * dispatch, event, and command-response handlers.
     *
     * @param text The error or diagnostic text to record.
     * 
     * @see #saveErrText
     * @see #errSB
     */
    protected synchronized void err(final String text)
    {
        if (this.saveErrText)               this.errSB.append(text);
        if (this.userProvidedErr != null)   this.userProvidedErr.append(text);
    }

    /**
     * Ensures that a user-provided {@link Appendable} is safe for unchecked logging calls.
     *
     * <BR /><BR />
     * If the appendable is already an {@link AppendableSafe}, it is returned directly.  Otherwise,
     * it is wrapped so that {@link IOException} may be handled by
     * {@link #handleLogIOE(IOException)}.
     * A {@code null} value is preserved as {@code null}.
     *
     * @param a The appendable to check or wrap.
     * @return An {@link AppendableSafe} wrapper, the original wrapper, or {@code null}.
     */
    protected static AppendableSafe checkIsInstance(final Appendable a)
    {
        return (a == null)
            ? null
            : (a instanceof AppendableSafe)
                ? (AppendableSafe) a
                : new AppendableSafe(a, ConnRecord::handleLogIOE);
    }

    /**
     * Handles an {@link IOException} thrown while writing diagnostic text.
     *
     * <BR /><BR />
     * Logging failures are treated as unreachable Browser Tool failures.  The Browser Tool expects
     * its own diagnostic stream plumbing to be reliable once it has been installed.
     *
     * @param ioe The exception thrown by the underlying appendable.
     */
    private static void handleLogIOE(final IOException ioe)
    {
        System.err.println(
            "There has been an exception throw while attempting to write to an output log.\n" +
            "Cannot Proceed"
        );

        throw new UnreachableError(ioe);
    }

}