Skip to content

Commit fd9580b

Browse files
authored
Add EventSource (#51)
Signed-off-by: Kevin Poirot <kevin@vazee.fr>
1 parent 6845b62 commit fd9580b

File tree

4 files changed

+155
-7
lines changed

4 files changed

+155
-7
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
# React LogViewer
88

9-
React component that loads and views remote text in the browser lazily and efficiently. Logs can be loaded from static text, a URL, or a WebSocket and including ANSI highlighting.
9+
React component that loads and views remote text in the browser lazily and efficiently. Logs can be loaded from static text, a URL, a WebSocket, or an EventSource and including ANSI highlighting.
1010
Forked from [mozilla-frontend-infra/react-lazylog](https://github.com/mozilla-frontend-infra/react-lazylog).
1111

1212
**If you like this project, please consider supporting me ❤️**

src/components/LazyLog/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,22 @@ let socket = null;
6363
</div>
6464
</div>
6565
```
66+
67+
Log viewing using an eventsource
68+
69+
```jsx harmony
70+
const url = 'https://my.eventsource.tld';
71+
72+
<div>
73+
<div style={{ height: 200, width: 902 }}>
74+
<LazyLog
75+
enableSearch
76+
url={url}
77+
eventsource
78+
eventsourceOptions={{
79+
formatMessage: e => JSON.parse(e).message,
80+
}}
81+
/>
82+
</div>
83+
</div>
84+
```

src/components/LazyLog/index.tsx

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
searchFormatPart,
2121
} from "../Utils/utils";
2222
import websocket from "../Utils/websocket";
23+
import eventsource from "../Utils/eventsource";
2324
import styles from "./index.module.css";
2425

2526
export interface WebsocketOptions {
@@ -36,7 +37,7 @@ export interface WebsocketOptions {
3637
*/
3738
onError?: ((e: Event) => void) | undefined;
3839
/**
39-
* Callback allback which formats the websocket data stream.
40+
* Callback which formats the websocket data stream.
4041
*/
4142
formatMessage?: ((message: any) => string) | undefined;
4243
/**
@@ -50,6 +51,38 @@ export interface WebsocketOptions {
5051
reconnectWait?: number;
5152
}
5253

54+
export interface EventSourceOptions {
55+
/**
56+
* Boolean indicating if CORS should be set to include credentials
57+
*/
58+
withCredentials?: boolean;
59+
/**
60+
* Callback when the eventsource is opened
61+
*/
62+
onOpen?: ((e: Event, eventSource: EventSource) => void) | undefined;
63+
/**
64+
* Callback when the eventsource is closed
65+
*/
66+
onClose?: ((e: Event) => void) | undefined;
67+
/**
68+
* Callback when the eventsource has an error
69+
*/
70+
onError?: ((e: Event) => void) | undefined;
71+
/**
72+
* Callback which formats the eventsource data stream.
73+
*/
74+
formatMessage?: ((message: any) => string) | undefined;
75+
/**
76+
* Set to true, to reconnect the EventSource automatically.
77+
*/
78+
reconnect?: boolean;
79+
/**
80+
* Set the time to wait between reconnects in seconds.
81+
* Default is 1s
82+
*/
83+
reconnectWait?: number;
84+
}
85+
5386
export interface ErrorStatus extends Error {
5487
/**
5588
* Status code
@@ -262,6 +295,15 @@ export interface LazyLogProps {
262295
* Options object which will be passed through to websocket.
263296
*/
264297
websocketOptions?: WebsocketOptions;
298+
/**
299+
* Set to `true` to specify that url is an eventsource URL.
300+
* Defaults to `false` to download data until completion.
301+
*/
302+
eventsource?: boolean;
303+
/**
304+
* Options object which will be passed through to evensource.
305+
*/
306+
eventsourceOptions?: EventSourceOptions;
265307
/**
266308
* Set the width in pixels for the component.
267309
* Defaults to `'auto'` if unspecified.
@@ -331,6 +373,8 @@ export default class LazyLog extends Component<LazyLogProps, LazyLogState> {
331373
style: {},
332374
websocket: false,
333375
websocketOptions: {},
376+
eventsource: false,
377+
eventsourceOptions: {},
334378
width: "auto",
335379
};
336380

@@ -477,15 +521,21 @@ export default class LazyLog extends Component<LazyLogProps, LazyLogState> {
477521
const {
478522
stream: isStream,
479523
websocket: isWebsocket,
524+
eventsource: isEventsource,
480525
url,
481526
fetchOptions,
482527
websocketOptions,
528+
eventsourceOptions,
483529
} = this.props;
484530

485531
if (isWebsocket) {
486532
return websocket(url!, websocketOptions!);
487533
}
488534

535+
if (isEventsource) {
536+
return eventsource(url!, eventsourceOptions!);
537+
}
538+
489539
if (isStream) {
490540
return stream(url!, fetchOptions);
491541
}
@@ -529,10 +579,10 @@ export default class LazyLog extends Component<LazyLogProps, LazyLogState> {
529579

530580
handleUpdate = ({ lines: moreLines, encodedLog }: any) => {
531581
this.encodedLog = encodedLog;
532-
const { scrollToLine, follow, stream, websocket } = this.props;
582+
const { scrollToLine, follow, stream, websocket, eventsource } = this.props;
533583

534-
// handle stream and socket updates batched update mode
535-
if (stream || websocket) {
584+
// handle stream, socket and eventsource updates batched update mode
585+
if (stream || websocket || eventsource) {
536586
this.setState((state, props) => {
537587
const { scrollToLine, follow } = props;
538588
const { count: previousCount } = state;
@@ -736,9 +786,9 @@ export default class LazyLog extends Component<LazyLogProps, LazyLogState> {
736786

737787
handleSearch = (keywords: string | undefined) => {
738788
const { resultLines, searchKeywords } = this.state;
739-
const { caseInsensitive, stream, websocket } = this.props;
789+
const { caseInsensitive, stream, websocket, eventsource } = this.props;
740790
const currentResultLines =
741-
!stream && !websocket && keywords === searchKeywords
791+
!stream && !websocket && !eventsource && keywords === searchKeywords
742792
? resultLines
743793
: searchLines(keywords, this.encodedLog!, caseInsensitive!);
744794

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { List } from "immutable";
2+
import mitt from "mitt";
3+
4+
import { EventSourceOptions } from "../LazyLog";
5+
import { encode } from "./encoding";
6+
import { bufferConcat, convertBufferToLines } from "./utils";
7+
8+
export default (url: string | URL, options: EventSourceOptions) => {
9+
const { withCredentials, onOpen, onClose, onError, formatMessage } = options;
10+
const emitter = mitt();
11+
let encodedLog = new Uint8Array();
12+
let overage: any = null;
13+
let aborted: boolean = false;
14+
15+
emitter.on("data", (data) => {
16+
encodedLog = bufferConcat(
17+
encodedLog,
18+
encode(data as unknown as string)
19+
);
20+
21+
const { lines, remaining } = convertBufferToLines(
22+
encode(data as unknown as string),
23+
overage
24+
);
25+
26+
overage = remaining;
27+
28+
emitter.emit("update", { lines, encodedLog });
29+
});
30+
31+
emitter.on("done", () => {
32+
if (overage) {
33+
emitter.emit("update", { lines: List.of(overage), encodedLog });
34+
}
35+
36+
emitter.emit("end", encodedLog);
37+
});
38+
39+
emitter.on("start", () => {
40+
try {
41+
// try to connect to eventSource
42+
const eventSource = new EventSource(url, { withCredentials });
43+
44+
eventSource.addEventListener("open", (e) => {
45+
// relay on open events if a handler is registered
46+
onOpen && onOpen(e, eventSource);
47+
});
48+
49+
eventSource.addEventListener("close", (e) => {
50+
onClose && onClose(e);
51+
if(!aborted && options.reconnect) {
52+
const timeout = options.reconnectWait ?? 1;
53+
setTimeout(() => emitter.emit("start"), timeout*1000);
54+
}
55+
});
56+
57+
eventSource.addEventListener("error", (err) => {
58+
onError && onError(err);
59+
});
60+
61+
eventSource.addEventListener("message", (e) => {
62+
let msg = formatMessage ? formatMessage(e.data) : e.data;
63+
64+
if (typeof msg !== "string") {
65+
return;
66+
}
67+
// add a new line character between each message if one doesn't exist.
68+
// this allows our search index to properly distinguish new lines.
69+
msg = msg.endsWith("\n") ? msg : `${msg}\n`;
70+
71+
emitter.emit("data", msg);
72+
});
73+
} catch (err) {
74+
emitter.emit("error", err);
75+
}
76+
});
77+
78+
return emitter;
79+
};

0 commit comments

Comments
 (0)