react-native/RNTester/js/WebSocketExample.js

366 lines
8.7 KiB
JavaScript

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule WebSocketExample
* @format
*/
'use strict';
/* eslint-env browser */
const React = require('react');
const ReactNative = require('react-native');
const {
Image,
PixelRatio,
ScrollView,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} = ReactNative;
const DEFAULT_WS_URL = 'ws://localhost:5555/';
const DEFAULT_HTTP_URL = 'http://localhost:5556/';
const WS_EVENTS = ['close', 'error', 'message', 'open'];
const WS_STATES = [
/* 0 */ 'CONNECTING',
/* 1 */ 'OPEN',
/* 2 */ 'CLOSING',
/* 3 */ 'CLOSED',
];
class Button extends React.Component {
render(): React.Element<any> {
const label = (
<Text style={styles.buttonLabel}>
{this.props.label}
</Text>
);
if (this.props.disabled) {
return (
<View style={[styles.button, styles.disabledButton]}>
{label}
</View>
);
}
return (
<TouchableOpacity onPress={this.props.onPress} style={styles.button}>
{label}
</TouchableOpacity>
);
}
}
class Row extends React.Component {
render(): React.Element<any> {
return (
<View style={styles.row}>
<Text>
{this.props.label}
</Text>
{this.props.value
? <Text>
{this.props.value}
</Text>
: null}
{this.props.children}
</View>
);
}
}
class WebSocketImage extends React.Component {
ws: ?WebSocket = null;
state: {blob: ?Blob} = {blob: null};
componentDidMount() {
let ws = (this.ws = new WebSocket(this.props.url));
ws.binaryType = 'blob';
ws.onmessage = event => {
if (event.data instanceof Blob) {
const blob = event.data;
if (this.state.blob) {
this.state.blob.close();
}
this.setState({blob});
}
};
ws.onopen = event => {
ws.send('getImage');
};
}
componentUnmount() {
if (this.state.blob) {
this.state.blob.close();
}
this.ws && this.ws.close();
}
render() {
if (!this.state.blob) {
return <View />;
}
return (
<Image
source={{uri: URL.createObjectURL(this.state.blob)}}
style={{width: 50, height: 50}}
/>
);
}
}
function showValue(value) {
if (value === undefined || value === null) {
return '(no value)';
}
if (
typeof ArrayBuffer !== 'undefined' &&
typeof Uint8Array !== 'undefined' &&
value instanceof ArrayBuffer
) {
return `ArrayBuffer {${String(Array.from(new Uint8Array(value)))}}`;
}
return value;
}
type State = {
url: string,
httpUrl: string,
fetchStatus: ?string,
socket: ?WebSocket,
socketState: ?number,
lastSocketEvent: ?string,
lastMessage: ?string | ?ArrayBuffer,
outgoingMessage: string,
};
class WebSocketExample extends React.Component<any, any, State> {
static title = 'WebSocket';
static description = 'WebSocket API';
state: State = {
url: DEFAULT_WS_URL,
httpUrl: DEFAULT_HTTP_URL,
fetchStatus: null,
socket: null,
socketState: null,
lastSocketEvent: null,
lastMessage: null,
outgoingMessage: '',
};
_connect = () => {
const socket = new WebSocket(this.state.url);
WS_EVENTS.forEach(ev => socket.addEventListener(ev, this._onSocketEvent));
this.setState({
socket,
socketState: socket.readyState,
});
};
_disconnect = () => {
if (!this.state.socket) {
return;
}
this.state.socket.close();
};
_onSocketEvent = (event: MessageEvent) => {
const state: any = {
socketState: event.target.readyState,
lastSocketEvent: event.type,
};
if (event.type === 'message') {
state.lastMessage = event.data;
}
this.setState(state);
};
_sendText = () => {
if (!this.state.socket) {
return;
}
this.state.socket.send(this.state.outgoingMessage);
this.setState({outgoingMessage: ''});
};
_sendHttp = () => {
this.setState({
fetchStatus: 'fetching',
});
fetch(this.state.httpUrl).then(response => {
if (response.status >= 200 && response.status < 400) {
this.setState({
fetchStatus: 'OK',
});
}
});
};
_sendBinary = () => {
if (
!this.state.socket ||
typeof ArrayBuffer === 'undefined' ||
typeof Uint8Array === 'undefined'
) {
return;
}
const {outgoingMessage} = this.state;
const buffer = new Uint8Array(outgoingMessage.length);
for (let i = 0; i < outgoingMessage.length; i++) {
buffer[i] = outgoingMessage.charCodeAt(i);
}
this.state.socket.send(buffer);
this.setState({outgoingMessage: ''});
};
render(): React.Element<any> {
const socketState = WS_STATES[this.state.socketState || -1];
const canConnect =
!this.state.socket || this.state.socket.readyState >= WebSocket.CLOSING;
const canSend = socketState === 'OPEN';
return (
<ScrollView style={styles.container}>
<View style={styles.note}>
<Text>To start the WS test server:</Text>
<Text style={styles.monospace}>
./RNTester/js/websocket_test_server.js
</Text>
</View>
<Row label="Current WebSocket state" value={showValue(socketState)} />
<Row
label="Last WebSocket event"
value={showValue(this.state.lastSocketEvent)}
/>
<Row
label="Last message received"
value={showValue(this.state.lastMessage)}
/>
<Row label="Last image received">
{canSend ? <WebSocketImage url={this.state.url} /> : null}
</Row>
<TextInput
style={styles.textInput}
autoCorrect={false}
placeholder="Server URL..."
onChangeText={url => this.setState({url})}
value={this.state.url}
/>
<View style={styles.buttonRow}>
<Button
onPress={this._connect}
label="Connect"
disabled={!canConnect}
/>
<Button
onPress={this._disconnect}
label="Disconnect"
disabled={canConnect}
/>
</View>
<TextInput
style={styles.textInput}
autoCorrect={false}
placeholder="Type message here..."
onChangeText={outgoingMessage => this.setState({outgoingMessage})}
value={this.state.outgoingMessage}
/>
<View style={styles.buttonRow}>
<Button
onPress={this._sendText}
label="Send as text"
disabled={!canSend}
/>
<Button
onPress={this._sendBinary}
label="Send as binary"
disabled={!canSend}
/>
</View>
<View style={styles.note}>
<Text>To start the HTTP test server:</Text>
<Text style={styles.monospace}>
./RNTester/js/http_test_server.js
</Text>
</View>
<TextInput
style={styles.textInput}
autoCorrect={false}
placeholder="HTTP URL..."
onChangeText={httpUrl => this.setState({httpUrl})}
value={this.state.httpUrl}
/>
<View style={styles.buttonRow}>
<Button
onPress={this._sendHttp}
label="Send HTTP request to set cookie"
disabled={this.state.fetchStatus === 'fetching'}
/>
</View>
<View style={styles.note}>
<Text>
{this.state.fetchStatus === 'OK'
? 'Done. Check your WS server console to see if the next WS requests include the cookie (should be "wstest=OK")'
: '-'}
</Text>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
note: {
padding: 8,
margin: 4,
backgroundColor: 'white',
},
monospace: {
fontFamily: 'courier',
fontSize: 11,
},
row: {
height: 40,
padding: 4,
backgroundColor: 'white',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
borderBottomWidth: 1 / PixelRatio.get(),
borderColor: 'grey',
},
button: {
margin: 8,
padding: 8,
borderRadius: 4,
backgroundColor: 'blue',
alignSelf: 'center',
},
disabledButton: {
opacity: 0.5,
},
buttonLabel: {
color: 'white',
},
buttonRow: {
flexDirection: 'row',
justifyContent: 'center',
},
textInput: {
height: 40,
backgroundColor: 'white',
margin: 8,
padding: 8,
},
});
module.exports = WebSocketExample;