@flashphoner/websdk 2.0.202 → 2.0.207

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/docTemplate/README.md +1 -1
  2. package/examples/demo/sip/phone/phone.js +7 -10
  3. package/examples/demo/streaming/hls-player/hls-player.html +1 -4
  4. package/examples/demo/streaming/hls-player/hls-player.js +19 -1
  5. package/examples/demo/streaming/hls-player/player-page.html +1 -1
  6. package/examples/demo/streaming/hls-player/video-js.css +142 -51
  7. package/examples/demo/streaming/hls-player/video.js +27294 -20390
  8. package/examples/demo/streaming/hls-player/video.min.js +27 -0
  9. package/examples/demo/streaming/media_devices_manager/manager.js +27 -1
  10. package/examples/demo/streaming/media_devices_manager/media_device_manager.html +7 -0
  11. package/examples/demo/streaming/stream-auto-restore/stream-auto-restore.css +23 -0
  12. package/examples/demo/streaming/stream-auto-restore/stream-auto-restore.html +76 -0
  13. package/examples/demo/streaming/stream-auto-restore/stream-auto-restore.js +356 -0
  14. package/examples/typescript/two-way-streaming-ts/.gitignore +23 -0
  15. package/examples/typescript/two-way-streaming-ts/README.md +36 -0
  16. package/examples/typescript/two-way-streaming-ts/package.json +45 -0
  17. package/examples/typescript/two-way-streaming-ts/public/favicon.ico +0 -0
  18. package/examples/typescript/two-way-streaming-ts/public/index.html +33 -0
  19. package/examples/typescript/two-way-streaming-ts/public/logo192.png +0 -0
  20. package/examples/typescript/two-way-streaming-ts/public/logo512.png +0 -0
  21. package/examples/typescript/two-way-streaming-ts/public/manifest.json +25 -0
  22. package/examples/typescript/two-way-streaming-ts/public/media/preloader.mp4 +0 -0
  23. package/examples/typescript/two-way-streaming-ts/public/robots.txt +3 -0
  24. package/examples/typescript/two-way-streaming-ts/src/TwoWayStreamingApp.css +23 -0
  25. package/examples/typescript/two-way-streaming-ts/src/TwoWayStreamingApp.tsx +371 -0
  26. package/examples/typescript/two-way-streaming-ts/src/fp-utils.ts +117 -0
  27. package/examples/typescript/two-way-streaming-ts/src/index.css +13 -0
  28. package/examples/typescript/two-way-streaming-ts/src/index.tsx +9 -0
  29. package/examples/typescript/two-way-streaming-ts/tsconfig.json +26 -0
  30. package/flashphoner-no-flash.js +19 -19
  31. package/flashphoner-no-flash.min.js +2 -2
  32. package/flashphoner-no-webrtc.js +18 -18
  33. package/flashphoner-no-webrtc.min.js +1 -1
  34. package/flashphoner-no-wsplayer.js +20 -20
  35. package/flashphoner-no-wsplayer.min.js +2 -2
  36. package/flashphoner-room-api.js +6 -6
  37. package/flashphoner-room-api.min.js +2 -2
  38. package/flashphoner-temasys-flash-websocket-without-adapterjs.js +20 -20
  39. package/flashphoner-temasys-flash-websocket.js +20 -20
  40. package/flashphoner-temasys-flash-websocket.min.js +1 -1
  41. package/flashphoner-webrtc-only.js +17 -17
  42. package/flashphoner-webrtc-only.min.js +1 -1
  43. package/flashphoner.js +20 -20
  44. package/flashphoner.min.js +2 -2
  45. package/package.json +2 -1
  46. package/src/constants.d.ts +1 -0
  47. package/src/flashphoner-core.d.ts +192 -0
  48. package/src/flashphoner-core.js +4 -4
  49. package/src/room-module.d.ts +29 -0
  50. package/src/webrtc-media-provider.js +2 -2
  51. package/examples/demo/streaming/hls-player/videojs-hls.min.js +0 -27
@@ -0,0 +1,25 @@
1
+ {
2
+ "short_name": "Two Way Streaming",
3
+ "name": "Two Way Streaming React App Sample",
4
+ "icons": [
5
+ {
6
+ "src": "favicon.ico",
7
+ "sizes": "64x64 32x32 24x24 16x16",
8
+ "type": "image/x-icon"
9
+ },
10
+ {
11
+ "src": "logo192.png",
12
+ "type": "image/png",
13
+ "sizes": "192x192"
14
+ },
15
+ {
16
+ "src": "logo512.png",
17
+ "type": "image/png",
18
+ "sizes": "512x512"
19
+ }
20
+ ],
21
+ "start_url": ".",
22
+ "display": "standalone",
23
+ "theme_color": "#000000",
24
+ "background_color": "#ffffff"
25
+ }
@@ -0,0 +1,3 @@
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
@@ -0,0 +1,23 @@
1
+ .fp-Video {
2
+ border: 1px double black;
3
+ width: 322px;
4
+ height: 242px;
5
+ text-align: center;
6
+ background: #c0c0c0;
7
+ margin: 0 auto 0 auto;
8
+ }
9
+
10
+ .display {
11
+ width: 100%;
12
+ height: 100%;
13
+ display: inline-block;
14
+ }
15
+
16
+ .display > video, object {
17
+ width: 100%;
18
+ height: 100%;
19
+ }
20
+
21
+ video:-webkit-full-screen {
22
+ border-radius: 1px;
23
+ }
@@ -0,0 +1,371 @@
1
+ import React, { Component } from 'react';
2
+ import './TwoWayStreamingApp.css';
3
+ import 'bootstrap/dist/css/bootstrap.min.css';
4
+ import * as FPUtils from './fp-utils';
5
+ import * as Flashphoner from '@flashphoner/websdk';
6
+
7
+ const SESSION_STATUS = Flashphoner.constants.SESSION_STATUS;
8
+ const STREAM_STATUS = Flashphoner.constants.STREAM_STATUS;
9
+ const Browser = Flashphoner.Browser;
10
+ const PRELOADER_URL = process.env.PUBLIC_URL + '/media/preloader.mp4';
11
+
12
+ interface TwoWayStreamingState {
13
+ apiFailed: string,
14
+ session: any,
15
+ sessionStatus: string,
16
+ sessionStatusClass: string,
17
+ localVideo: any,
18
+ remoteVideo: any,
19
+ publishStream: any,
20
+ publishStatus: string,
21
+ publishStatusClass: string,
22
+ playStream: any,
23
+ playStatus: string,
24
+ playStatusClass: string,
25
+ publishStreamName: string,
26
+ publishStreamNameDisabled: boolean,
27
+ playStreamName: string,
28
+ playStreamNameDisabled: boolean,
29
+ connectButtonText: string,
30
+ connectButtonDisabled: boolean,
31
+ serverUrl: string,
32
+ serverUrlDisabled: boolean,
33
+ publishButtonText: string,
34
+ publishButtonDisabled: boolean,
35
+ playButtonText: string,
36
+ playButtonDisabled: boolean
37
+ }
38
+
39
+ class TwoWayStreamingApp extends Component<{}, TwoWayStreamingState> {
40
+ constructor(props: any) {
41
+ super(props);
42
+ this.state = {
43
+ apiFailed: '',
44
+ session: null,
45
+ sessionStatus: '',
46
+ sessionStatusClass: 'text-muted',
47
+ localVideo: null,
48
+ remoteVideo: null,
49
+ publishStream: null,
50
+ publishStatus: '',
51
+ publishStatusClass: 'text-muted',
52
+ playStream: null,
53
+ playStatus: '',
54
+ playStatusClass: 'text-muted',
55
+ publishStreamName: 'streamName',
56
+ publishStreamNameDisabled: true,
57
+ playStreamName: 'streamName',
58
+ playStreamNameDisabled: true,
59
+ connectButtonText: 'Connect',
60
+ connectButtonDisabled: false,
61
+ serverUrl: 'wss://demo.flashphoner.com:8443',
62
+ serverUrlDisabled: false,
63
+ publishButtonText: 'Publish',
64
+ publishButtonDisabled: true,
65
+ playButtonText: 'Play',
66
+ playButtonDisabled: true
67
+ };
68
+ }
69
+
70
+ componentDidMount() {
71
+ try {
72
+ Flashphoner.init({});
73
+ this.setState({
74
+ localVideo: document.getElementById('localVideo'),
75
+ remoteVideo: document.getElementById('remoteVideo')
76
+ });
77
+ }
78
+ catch(e) {
79
+ console.log(e);
80
+ this.setState({
81
+ apiFailed: 'Your browser does not support WebRTC technology needed for this example',
82
+ connectButtonDisabled: true,
83
+ serverUrlDisabled: true
84
+ });
85
+ }
86
+ }
87
+
88
+ onConnected = (session: any) => {
89
+ this.setState({
90
+ session: session,
91
+ connectButtonText: 'Disconnect',
92
+ connectButtonDisabled: false,
93
+ serverUrlDisabled: true
94
+ });
95
+ this.onUnpublished();
96
+ this.onStopped();
97
+ }
98
+
99
+ onDisconnected = () => {
100
+ this.setState({
101
+ session: null,
102
+ connectButtonText: 'Connect',
103
+ connectButtonDisabled: false,
104
+ serverUrlDisabled: false
105
+ });
106
+ this.onUnpublished();
107
+ this.onStopped();
108
+ }
109
+
110
+ onPublishing = (stream: any) => {
111
+ this.setState({
112
+ publishStream: stream,
113
+ publishButtonText: 'Unpublish',
114
+ publishButtonDisabled: false
115
+ });
116
+ }
117
+
118
+ onUnpublished = () => {
119
+ let session = this.state.session;
120
+ let itemState = true;
121
+
122
+ if(session && session.status() === SESSION_STATUS.ESTABLISHED) {
123
+ itemState = false;
124
+ }
125
+ this.setState({
126
+ publishStream: null,
127
+ publishButtonText: 'Publish',
128
+ publishButtonDisabled: itemState,
129
+ publishStreamNameDisabled: itemState
130
+ });
131
+ }
132
+
133
+ onPlaying = (stream: any) => {
134
+ this.setState({
135
+ playStream: stream,
136
+ playButtonText: 'Stop',
137
+ playButtonDisabled: false
138
+ });
139
+ }
140
+
141
+ onStopped = () => {
142
+ let session = this.state.session;
143
+ let itemState = true;
144
+
145
+ if(session && session.status() === SESSION_STATUS.ESTABLISHED) {
146
+ itemState = false;
147
+ }
148
+ this.setState({
149
+ playStream: null,
150
+ playButtonText: 'Play',
151
+ playButtonDisabled: itemState,
152
+ playStreamNameDisabled: itemState
153
+ });
154
+ }
155
+
156
+ publishStream = () => {
157
+ let app = this;
158
+ let session = this.state.session;
159
+ let streamName = this.state.publishStreamName;
160
+ let localVideo = this.state.localVideo;
161
+
162
+ if(session && localVideo) {
163
+ session.createStream({
164
+ name: streamName,
165
+ display: localVideo,
166
+ cacheLocalResources: true,
167
+ receiveVideo: false,
168
+ receiveAudio: false
169
+ }).on(STREAM_STATUS.PUBLISHING, (stream: any) => {
170
+ app.setState({publishStatus: STREAM_STATUS.PUBLISHING, publishStatusClass: 'text-success'});
171
+ app.onPublishing(stream);
172
+ }).on(STREAM_STATUS.UNPUBLISHED, () => {
173
+ app.setState({publishStatus: STREAM_STATUS.UNPUBLISHED, publishStatusClass: 'text-success'});
174
+ app.onUnpublished();
175
+ }).on(STREAM_STATUS.FAILED, () => {
176
+ app.setState({publishStatus: STREAM_STATUS.FAILED, publishStatusClass: 'text-danger'});
177
+ app.onUnpublished();
178
+ }).publish();
179
+ }
180
+ }
181
+
182
+ playStream = () => {
183
+ let app = this;
184
+ let session = this.state.session;
185
+ let streamName = this.state.playStreamName;
186
+ let remoteVideo = this.state.remoteVideo;
187
+
188
+ if(session && remoteVideo) {
189
+ session.createStream({
190
+ name: streamName,
191
+ display: remoteVideo
192
+ }).on(STREAM_STATUS.PENDING, (stream: any) => {
193
+ let video: any = document.getElementById(stream.id());
194
+ if (!video.hasListeners) {
195
+ video.hasListeners = true;
196
+ video.addEventListener('resize', (event: any) => {
197
+ FPUtils.resizeVideo(event.target);
198
+ });
199
+ }
200
+ }).on(STREAM_STATUS.PLAYING, (stream: any) => {
201
+ app.setState({playStatus: STREAM_STATUS.PLAYING, playStatusClass: 'text-success'});
202
+ app.onPlaying(stream);
203
+ }).on(STREAM_STATUS.STOPPED, () => {
204
+ app.setState({playStatus: STREAM_STATUS.STOPPED, playStatusClass: 'text-success'});
205
+ app.onStopped();
206
+ }).on(STREAM_STATUS.FAILED, () => {
207
+ app.setState({playStatus: STREAM_STATUS.FAILED, playStatusClass: 'text-danger'});
208
+ app.onStopped();
209
+ }).play();
210
+ }
211
+ }
212
+
213
+ onConnectClick = () => {
214
+ let app = this;
215
+ let url = this.state.serverUrl;
216
+ let session = this.state.session;
217
+
218
+ if (!session) {
219
+ console.log("Create new session with url " + url);
220
+ app.setState({connectButtonDisabled: true, serverUrlDisabled: true});
221
+ Flashphoner.createSession({urlServer: url}).on(SESSION_STATUS.ESTABLISHED, (session: any) => {
222
+ app.setState({sessionStatus: SESSION_STATUS.ESTABLISHED, sessionStatusClass: 'text-success'});
223
+ app.onConnected(session);
224
+ }).on(SESSION_STATUS.DISCONNECTED, () => {
225
+ app.setState({sessionStatus: SESSION_STATUS.DISCONNECTED, sessionStatusClass: 'text-success'});
226
+ app.onDisconnected();
227
+ }).on(SESSION_STATUS.FAILED, () => {
228
+ app.setState({sessionStatus: SESSION_STATUS.FAILED, sessionStatusClass: 'text-danger'});
229
+ app.onDisconnected();
230
+ });
231
+ } else {
232
+ app.setState({connectButtonDisabled: true});
233
+ session.disconnect();
234
+ }
235
+ }
236
+
237
+ onPublishClick = () => {
238
+ let app = this;
239
+ let stream = this.state.publishStream;
240
+ let localVideo = this.state.localVideo;
241
+
242
+ if (!localVideo) return;
243
+ if (!stream) {
244
+ app.setState({publishButtonDisabled: true, publishStreamNameDisabled: true});
245
+ if (Browser.isSafariWebRTC()) {
246
+ Flashphoner.playFirstVideo(localVideo, true, PRELOADER_URL).then(() => {
247
+ app.publishStream();
248
+ });
249
+ return;
250
+ }
251
+ app.publishStream();
252
+ } else {
253
+ app.setState({publishButtonDisabled: true});
254
+ stream.stop();
255
+ }
256
+ }
257
+
258
+ onPlayClick = () => {
259
+ let app = this;
260
+ let stream = this.state.playStream;
261
+ let remoteVideo = this.state.remoteVideo;
262
+
263
+ if (!remoteVideo) return;
264
+ if (!stream) {
265
+ app.setState({playButtonDisabled: true, playStreamNameDisabled: true});
266
+ if (Flashphoner.getMediaProviders()[0] === "WSPlayer") {
267
+ Flashphoner.playFirstSound();
268
+ } else if (Browser.isSafariWebRTC()) {
269
+ Flashphoner.playFirstVideo(remoteVideo, false, PRELOADER_URL).then(() => {
270
+ app.playStream();
271
+ });
272
+ return;
273
+ }
274
+ app.playStream();
275
+ } else {
276
+ app.setState({playButtonDisabled: true});
277
+ stream.stop();
278
+ }
279
+ }
280
+
281
+ render() {
282
+ return (
283
+ <div className="container">
284
+ <h2 className="text-danger">{this.state.apiFailed}</h2>
285
+ <div className="row">
286
+ <h2 className="text-center">Two-way Streaming in Typescript</h2>
287
+ </div>
288
+ <div className="row row-space">
289
+ <div className="col-sm-6">
290
+ <div className="text-center text-muted">Local</div>
291
+ <div className="fp-Video">
292
+ <div id="localVideo" className="display"></div>
293
+ </div>
294
+ <div className="input-group col-sm-5" style={{margin: '10px auto 0 auto'}}>
295
+ <input type="text"
296
+ className="form-control"
297
+ placeholder="Stream Name"
298
+ value={this.state.publishStreamName}
299
+ disabled={this.state.publishStreamNameDisabled}
300
+ onChange={(event) => this.setState({publishStreamName: event.target.value})}
301
+ />
302
+ <div className="input-group-btn">
303
+ <button
304
+ className="btn btn-outline-dark"
305
+ disabled={this.state.publishButtonDisabled}
306
+ onClick={() => this.onPublishClick()}>
307
+ {this.state.publishButtonText}
308
+ </button>
309
+ </div>
310
+ </div>
311
+ <div className="text-center" style={{marginTop: '20px'}}>
312
+ <div className={this.state.publishStatusClass}>{this.state.publishStatus}</div>
313
+ </div>
314
+ </div>
315
+ <div className="col-sm-6">
316
+ <div className="text-center text-muted">Remote</div>
317
+ <div className="fp-Video">
318
+ <div id="remoteVideo" className="display"></div>
319
+ </div>
320
+ <div className="input-group col-sm-5" style={{margin: '10px auto 0 auto'}}>
321
+ <input type="text"
322
+ className="form-control"
323
+ placeholder="Stream Name"
324
+ value={this.state.playStreamName}
325
+ disabled={this.state.playStreamNameDisabled}
326
+ onChange={(event) => this.setState({playStreamName: event.target.value})}
327
+ />
328
+ <div className="input-group-btn">
329
+ <button
330
+ className="btn btn-outline-dark"
331
+ disabled={this.state.playButtonDisabled}
332
+ onClick={() => this.onPlayClick()}>
333
+ {this.state.playButtonText}
334
+ </button>
335
+ </div>
336
+ </div>
337
+ <div className="text-center" style={{marginTop: '20px'}}>
338
+ <div className={this.state.playStatusClass}>{this.state.playStatus}</div>
339
+ </div>
340
+ </div>
341
+ </div>
342
+ <div className="row row-space">
343
+ <div className="col-sm-6 offset-sm-3">
344
+ <div className="input-group col-sm-5">
345
+ <input type="text"
346
+ className="form-control"
347
+ placeholder="Server Url"
348
+ value={this.state.serverUrl}
349
+ disabled={this.state.serverUrlDisabled}
350
+ onChange={(event) => this.setState({serverUrl: event.target.value})}
351
+ />
352
+ <div className="input-group-btn">
353
+ <button
354
+ className="btn btn-outline-dark"
355
+ disabled={this.state.connectButtonDisabled}
356
+ onClick={() => this.onConnectClick()}>
357
+ {this.state.connectButtonText}
358
+ </button>
359
+ </div>
360
+ </div>
361
+ <div className="text-center" style={{marginTop: '20px'}}>
362
+ <div className={this.state.sessionStatusClass}>{this.state.sessionStatus}</div>
363
+ </div>
364
+ </div>
365
+ </div>
366
+ </div>
367
+ );
368
+ };
369
+ }
370
+
371
+ export default TwoWayStreamingApp;
@@ -0,0 +1,117 @@
1
+ // eslint-disable-next-line
2
+ 'use strict';
3
+ import { constants } from '@flashphoner/websdk';
4
+ const STREAM_STATUS = constants.STREAM_STATUS;
5
+
6
+ // Set default websocket URL
7
+ export function setURL(): string {
8
+ let proto: string;
9
+ let url: string;
10
+ let port: string;
11
+ if (window.location.protocol === "http:") {
12
+ proto = "ws://";
13
+ port = "8080";
14
+ } else {
15
+ proto = "wss://";
16
+ port = "8443";
17
+ }
18
+
19
+ url = proto + window.location.hostname + ":" + port;
20
+ return url;
21
+ }
22
+
23
+ // Get URL parameter by name
24
+ export function getUrlParam(name: string): string | null {
25
+ let url = window.location.href;
26
+ // eslint-disable-next-line
27
+ name = name.replace(/[\[\]]/g, "\\$&");
28
+ let regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
29
+ results = regex.exec(url);
30
+ if (!results) return null;
31
+ if (!results[2]) return '';
32
+ return decodeURIComponent(results[2].replace(/\+/g, " "));
33
+ }
34
+
35
+ // Generate simple uuid
36
+ export function createUUID(length: number): string {
37
+ var s = [];
38
+ var hexDigits = "0123456789abcdef";
39
+ for (var i = 0; i < 36; i++) {
40
+ s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
41
+ }
42
+ s[14] = "4";
43
+ s[19] = hexDigits.substr((parseInt(s[19], 16) & 0x3) | 0x8, 1);
44
+ s[8] = s[13] = s[18] = s[23] = "-";
45
+
46
+ var uuid = s.join("");
47
+
48
+ return uuid.substring(0, length);
49
+ }
50
+
51
+ // Helper function to downscale picture size
52
+ export function downScaleToFitSize(videoWidth: number, videoHeight: number, dstWidth: number, dstHeight: number): any {
53
+ let newWidth: number, newHeight: number;
54
+ var videoRatio = videoWidth / videoHeight;
55
+ var dstRatio = dstWidth / dstHeight;
56
+ if (dstRatio > videoRatio) {
57
+ newHeight = dstHeight;
58
+ newWidth = Math.floor(videoRatio * dstHeight);
59
+ } else {
60
+ newWidth = dstWidth;
61
+ newHeight = Math.floor(dstWidth / videoRatio);
62
+ }
63
+ return {
64
+ w: newWidth,
65
+ h: newHeight
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Resize video object to fit parent div.
71
+ * Div structure: div WxH -> div wrapper (display) -> video
72
+ * @param video HTML element from resize event target
73
+ */
74
+ export function resizeVideo(video: any, width?: number, height?: number) {
75
+ if (!video.parentNode) {
76
+ return;
77
+ }
78
+ let videoWidth = video.width;
79
+ let videoHeight = video.height;
80
+ if (video instanceof HTMLVideoElement) {
81
+ videoWidth = video.videoWidth;
82
+ videoHeight = video.videoHeight;
83
+ }
84
+ var display = video.parentNode;
85
+ var parentSize = {
86
+ w: display.parentNode.clientWidth,
87
+ h: display.parentNode.clientHeight
88
+ };
89
+ let newSize: any;
90
+ if (width && height) {
91
+ newSize = downScaleToFitSize(width, height, parentSize.w, parentSize.h);
92
+ } else {
93
+ newSize = downScaleToFitSize(videoWidth, videoHeight, parentSize.w, parentSize.h);
94
+ }
95
+ display.style.width = newSize.w + "px";
96
+ display.style.height = newSize.h + "px";
97
+
98
+ //vertical align
99
+ let margin = 0;
100
+ if (parentSize.h - newSize.h > 1) {
101
+ margin = Math.floor((parentSize.h - newSize.h) / 2);
102
+ }
103
+ display.style.margin = margin + "px auto";
104
+ console.log("Resize from " + videoWidth + "x" + videoHeight + " to " + display.offsetWidth + "x" + display.offsetHeight);
105
+ }
106
+
107
+ export function isPlaying(streamStatus: string): boolean {
108
+ switch(streamStatus) {
109
+ case STREAM_STATUS.PLAYING:
110
+ case STREAM_STATUS.RESIZE:
111
+ case STREAM_STATUS.SNAPSHOT_COMPLETE:
112
+ case STREAM_STATUS.NOT_ENOUGH_BANDWIDTH:
113
+ return true;
114
+ default:
115
+ return false;
116
+ }
117
+ }
@@ -0,0 +1,13 @@
1
+ body {
2
+ margin: 0;
3
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5
+ sans-serif;
6
+ -webkit-font-smoothing: antialiased;
7
+ -moz-osx-font-smoothing: grayscale;
8
+ }
9
+
10
+ code {
11
+ font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12
+ monospace;
13
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import './index.css';
4
+ import TwoWayStreamingApp from './TwoWayStreamingApp';
5
+
6
+ ReactDOM.render(
7
+ <TwoWayStreamingApp />,
8
+ document.getElementById('root')
9
+ );
@@ -0,0 +1,26 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es5",
4
+ "jsx": "react-jsx",
5
+ "module": "esnext",
6
+ "esModuleInterop": true,
7
+ "forceConsistentCasingInFileNames": true,
8
+ "strict": true,
9
+ "skipLibCheck": true,
10
+ "lib": [
11
+ "dom",
12
+ "dom.iterable",
13
+ "esnext"
14
+ ],
15
+ "allowJs": true,
16
+ "allowSyntheticDefaultImports": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "moduleResolution": "node",
19
+ "resolveJsonModule": true,
20
+ "isolatedModules": true,
21
+ "noEmit": true
22
+ },
23
+ "include": [
24
+ "src"
25
+ ]
26
+ }