Merge pull request #3696 from virtuacoplenny/lenny/stream-key-validation

Add some live stream key validation
This commit is contained in:
virtuacoplenny 2018-12-19 10:18:09 -08:00 committed by GitHub
commit 699b13066e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 150 additions and 21 deletions

View File

@ -65,6 +65,8 @@
}
.form-footer {
display: flex;
margin-top: 5px;
text-align: right;
}
@ -95,9 +97,15 @@
.stream-key-form {
.helper-link {
display: inline-block;
cursor: pointer;
margin-top: 5px;
display: inline-block;
flex-shrink: 0;
margin-left: auto;
}
.validation-error {
color:#FFD740;
font-size: 12px;
}
}
}

View File

@ -515,6 +515,7 @@
"expandedOn": "The meeting is currently being streamed to YouTube.",
"expandedPending": "The live streaming is being started...",
"failedToStart": "Live Streaming failed to start",
"invalidStreamKey": "Live stream key may be incorrect.",
"off": "Live Streaming stopped",
"on": "Live Streaming",
"pending": "Starting Live Stream...",

View File

@ -194,7 +194,8 @@ export default class AbstractStartLiveStreamDialog<P: Props>
*/
_onSubmit() {
const { broadcasts, selectedBoundStreamID } = this.state;
const key = this.state.streamKey || this.props._streamKey;
const key
= (this.state.streamKey || this.props._streamKey || '').trim();
if (!key) {
return false;

View File

@ -1,5 +1,6 @@
// @flow
import debounce from 'lodash/debounce';
import { Component } from 'react';
declare var interfaceConfig: Object;
@ -33,14 +34,27 @@ export type Props = {
value: string
};
/**
* The state of the component.
*/
type State = {
/**
* Whether or not to show the warnings that the passed in value seems like
* an improperly formatted stream key.
*/
showValidationError: boolean
};
/**
* An abstract React Component for entering a key for starting a YouTube live
* stream.
*
* @extends Component
*/
export default class AbstractStreamKeyForm extends Component<Props> {
export default class AbstractStreamKeyForm extends Component<Props, State> {
helpURL: string;
_debouncedUpdateValidationErrorVisibility: Function;
/**
* Constructor for the component.
@ -50,14 +64,45 @@ export default class AbstractStreamKeyForm extends Component<Props> {
constructor(props: Props) {
super(props);
this.state = {
showValidationError: Boolean(this.props.value)
&& !this._validateStreamKey(this.props.value)
};
this.helpURL = (typeof interfaceConfig !== 'undefined'
&& interfaceConfig.LIVE_STREAMING_HELP_LINK)
|| LIVE_STREAMING_HELP_LINK;
this._debouncedUpdateValidationErrorVisibility = debounce(
this._updateValidationErrorVisibility.bind(this),
800,
{ leading: false }
);
// Bind event handlers so they are only bound once per instance.
this._onInputChange = this._onInputChange.bind(this);
}
/**
* Implements React Component's componentDidUpdate.
*
* @inheritdoc
*/
componentDidUpdate(prevProps: Props) {
if (this.props.value !== prevProps.value) {
this._debouncedUpdateValidationErrorVisibility();
}
}
/**
* Implements React Component's componentWillUnmount.
*
* @inheritdoc
*/
componentWillUnmount() {
this._debouncedUpdateValidationErrorVisibility.cancel();
}
_onInputChange: Object => void
/**
@ -75,4 +120,38 @@ export default class AbstractStreamKeyForm extends Component<Props> {
this.props.onChange(value);
}
/**
* Checks if the stream key value seems like a valid stream key and sets the
* state for showing or hiding the notification about the stream key seeming
* invalid.
*
* @private
* @returns {boolean}
*/
_updateValidationErrorVisibility() {
const newShowValidationError = Boolean(this.props.value)
&& !this._validateStreamKey(this.props.value);
if (newShowValidationError !== this.state.showValidationError) {
this.setState({
showValidationError: newShowValidationError
});
}
}
/**
* Checks if a passed in stream key appears to be in a valid format.
*
* @param {string} streamKey - The stream key to check for valid formatting.
* @returns {void}
* @returns {boolean}
*/
_validateStreamKey(streamKey = '') {
const trimmedKey = streamKey.trim();
const fourGroupsDashSeparated = /^(?:[a-zA-Z0-9]{4}(?:-(?!$)|$)){4}/;
const match = fourGroupsDashSeparated.exec(trimmedKey);
return Boolean(match);
}
}

View File

@ -52,15 +52,28 @@ class StreamKeyForm extends AbstractStreamKeyForm {
placeholderTextColor = { PLACEHOLDER_COLOR }
style = { styles.streamKeyInput }
value = { this.props.value } />
<TouchableOpacity
onPress = { this._onOpenHelp }
style = { styles.streamKeyHelp } >
<Text style = { styles.text }>
{
t('liveStreaming.streamIdHelp')
}
</Text>
</TouchableOpacity>
<View style = { styles.formFooter }>
{
this.state.showValidationError
? <View style = { styles.formFooterItem }>
<Text style = { styles.warningText }>
{ t('liveStreaming.invalidStreamKey') }
</Text>
</View>
: null
}
<View style = { styles.formFooterItem }>
<TouchableOpacity
onPress = { this._onOpenHelp }
style = { styles.streamKeyHelp } >
<Text style = { styles.text }>
{
t('liveStreaming.streamIdHelp')
}
</Text>
</TouchableOpacity>
</View>
</View>
</View>
);
}

View File

@ -48,6 +48,20 @@ export default createStyleSheet({
padding: BoxModel.padding
},
/**
* Wrapper for the last element in the form.
*/
formFooter: {
flexDirection: 'row'
},
/**
* Wrapper for individual children in the last element of the form.
*/
formFooterItem: {
flex: 1
},
/**
* Explaining text on the top of the sign in form.
*/
@ -133,6 +147,12 @@ export default createStyleSheet({
text: {
color: ColorPalette.white
}
},
/**
* A different colored text to indicate information needing attention.
*/
warningText: {
color: ColorPalette.Y200
}
});

View File

@ -36,7 +36,7 @@ class StreamKeyForm extends AbstractStreamKeyForm {
* @returns {ReactElement}
*/
render() {
const { value, t } = this.props;
const { t, value } = this.props;
return (
<div className = 'stream-key-form'>
@ -52,16 +52,23 @@ class StreamKeyForm extends AbstractStreamKeyForm {
shouldFitContainer = { true }
type = 'text'
value = { this.props.value } />
{ this.helpURL
? <div className = 'form-footer'>
<a
<div className = 'form-footer'>
{
this.state.showValidationError
? <span className = 'validation-error'>
{ t('liveStreaming.invalidStreamKey') }
</span>
: null
}
{ this.helpURL
? <a
className = 'helper-link'
onClick = { this._onOpenHelp }>
{ t('liveStreaming.streamIdHelp') }
</a>
</div>
: null
}
: null
}
</div>
</div>
);
}