#386 [WIP] IRC registration flow
Closed 6 years ago by ryanlerch. Opened 6 years ago by sayanchowdhury.
sayanchowdhury/fedora-hubs feature/irc  into  develop

file modified
+1
@@ -63,6 +63,7 @@ 

      'hubs.widgets.github_pr:GitHubPRs',

      'hubs.widgets.githubissues:GitHubIssues',

      'hubs.widgets.halp:Halp',

+     'hubs.widgets.irc:IRC',

      'hubs.widgets.meetings:Meetings',

      'hubs.widgets.memberships:Memberships',

      'hubs.widgets.pagure_pr:PagurePRs',

@@ -0,0 +1,25 @@ 

+ import React from 'react';

+ 

+ 

+ export default class Config extends React.Component {

+ 

+   constructor(props) {

+     super(props);

+     this.state = {

+     };

+   }

+ 

+   render() {

+     return (

+       <div className="widget-irc-config">

+         <p>The content of the IRC widget config panel goes here.</p>

+         <p className="mb-0">Available URLs:</p>

+         <ul style={{listStyle: "inside"}}>

+           <li>{this.props.widget.urls.checkAvail}</li>

+           <li>{this.props.widget.urls.registerNickEmail}</li>

+         </ul>

+       </div>

+     );

+   }

+ 

+ }

@@ -0,0 +1,75 @@ 

+ import React from 'react';

+ import Spinner from '../../components/Spinner';

+ import Modal from '../../components/Modal';

+ 

+ export default class ModalChooseNickname extends React.Component {

+ 

+   constructor(props) {

+     super(props);

+     this.state = {

+       isNickValidCheck: false,

+       isNickValid: null,

+       validNickName: null,

+       formData: {

+         nick: null

+       }

+     };

+   }

+ 

+   handleChooseNickChange(nick) {

+     const formData = this.state.formData;

+     this.setState({isNickValidCheck: true});

+     this.serverRequest = $.ajax({

+       url: this.props.urls.checkAvail,

+       method: 'POST',

+       data: formData,

+       dataType: 'json',

+       success: function (data) {

+         this.setState({

+           isNickValidCheck: false,

+           isNickValid: true,

+           validNickName: data.nickname

+         });

+       }.bind(this),

+       error: function(xhr, status, err) {

+         this.setState({

+           isNickValidCheck: false,

+           isNickValid: false

+         });

+       }.bind(this)

+     });

+   }

+ 

+   render () {

+     let footer;

+     footer = null;

+ 

+     return (

+       <Modal

+         title={this.props.title}

+         footer={footer}

+         isLarge={true}

+         onCloseClicked={this.props.onCloseClicked}

+         cssClass="ModalChooseNickname"

+       >

+         <div className="">

+           <span className="">Choose a nickname</span>

+           <form id="choose-irc-nick" className="" method="post"

+                 onSubmit={this.handleChooseNickChange}>

+             <div className="form-group row">

+               <div className="col-md-6">

+                 <input id="choose-ircnick" type="text"

+                        className="form-control float-left" name="check-irc-nick"

+                        placeholder="irc nickname"/>

+               </div>

+               <div className="col-md-4">

+                 <button id="choose-ircnick-btn" type="submit"

+                         className="btn btn-secondary float-left"></button>

+               </div>

+             </div>

+           </form>

+         </div>

+       </Modal>

+     );

+   }

+ }

@@ -0,0 +1,71 @@ 

+ import React from 'react';

+ import PropTypes from 'prop-types';

+ import WidgetChrome from '../../components/WidgetChrome';

+ import {

+   defineMessages,

+   FormattedMessage,

+   } from 'react-intl';

+ 

+ import ModalChooseNickname from './ModalChooseNickname';

+ 

+ const messages = defineMessages({

+   choose_nickname: {

+     id: "hubs.widgets.irc.choose_nickname",

+     defaultMessage: "Choose your IRC nickname."

+   },

+ });

+ 

+ export default class Widget extends React.Component {

+ 

+   propTypes: {

+     widget: PropTypes.object.isRequired,

+     editMode: PropTypes.bool,

+     needsUpdate: PropTypes.bool,

+   }

+ 

+   constructor(props) {

+     super(props);

+     this.state = {

+       isLoading: false,

+       nickRegisterModalOpen: false

+     };

+     this.openNickRegisterModal = this.openNickRegisterModal.bind(this);

+     this.openNickRegisterModalClose = this.openNickRegisterModalClose.bind(this);

+     this.getNickRegisterModalTitle = this.getNickRegisterModalTitle.bind(this);

+   }

+ 

+   openNickRegisterModal(e) {

+     this.setState({nickRegisterModalOpen: true});

+   }

+ 

+   onNickRegisterModalClose(e) {

+     this.setState({nickRegisterModalOpen: false});

+   }

+ 

+   getNickRegisterModalTitle() {

+     return (

+       <FormattedMessage {...messages.choose_nickname} />

+     );

+   }

+ 

+   render() {

+     return (

+       <WidgetChrome

+         widget={this.props.widget}

+         editMode={this.props.editMode}

+         >

+         <div className="widget-irc">

+           The content of the IRC widget goes here.

+           <button className="btn btn-secondary" onClick={this.openNickRegisterModal}>Check Availability</button>

+         </div>

+         { this.state.nickRegisterModalOpen &&

+           <ModalChooseNickname

+             onCloseClicked={this.onNickRegisterModalClose}

+             title={this.getNickRegisterModalTitle}

+             urls={this.props.widget.urls}

+           />

+         }

+       </WidgetChrome>

+     );

+   }

+ }

@@ -0,0 +1,42 @@ 

+ from __future__ import unicode_literals, absolute_import

+ 

+ import logging

+ 

+ import flask

+ 

+ from hubs.widgets.base import Widget

+ from .views import check_avail_view, register_nick_view

+ 

+ 

+ log = logging.getLogger('hubs.widgets')

+ 

+ 

+ class IRC(Widget):

+ 

+     name = "irc"

+     label = "Chat"

+     position = "both"

+     parameters = []

+     is_react = True

+     views_module = ".views"

+ 

+     def register_routes(self, app):

+         super(IRC, self).register_routes(app)

+         # Add the views that are not specific to this instance.

+         # They won't be automatically picked up since they don't

+         # inherit from WidgetView.

+         rule = "/w/%s/check-avail" % self.name

+         endpoint = "%s_check_avail" % self.name

+         app.add_url_rule(rule, endpoint=endpoint, view_func=check_avail_view)

+         rule = "/w/%s/register-nick" % self.name

+         endpoint = "%s_register_nick" % self.name

+         app.add_url_rule(rule, endpoint=endpoint, view_func=register_nick_view)

+ 

+     def get_props(self, instance, *args, **kwargs):

+         props = super(IRC, self).get_props(instance, *args, **kwargs)

+         # Add the URLs to endpoints that the Javascript UI will use.

+         props["urls"] = dict(

+             checkAvail=flask.url_for("irc_check_avail"),

+             registerNickEmail=flask.url_for("irc_register_nick"),

+         )

+         return props

@@ -0,0 +1,40 @@ 

+ from __future__ import unicode_literals, absolute_import

+ 

+ 

+ import flask

+ 

+ 

+ class SessionEventLogger(object):

+     """

+     Logs events in the Flask session.

+     """

+     # This is just a suggestion for a way to manage the server-side

+     # state using the Flask session.

+     # I'm sure there are better ways to chain actions than using the

+     # `cb_next` argument, but Sayan knows best what the requirements

+     # are.

+ 

+     namespace = "widget-irc-events"

+ 

+     def on(self, event_name, cb_next=None):

+         def callback(connection, event):

+             if self.namespace not in flask.session:

+                 flask.session[self.namespace] = set()

+             flask.session[self.namespace].add(event_name)

+             flask.session.modified = True

+             if cb_next:

+                 return cb_next(connection)

+             else:

+                 return "NO_ERROR"

+         return callback

+ 

+     def has_happened(self, event_name):

+         if self.namespace not in flask.session:

+             return False

+         return event_name in flask.session[self.namespace]

+ 

+     def forget(self, event_name):

+         if self.namespace not in flask.session:

+             return

+         flask.session[self.namespace].remove(event_name)

+         flask.session.modified = True

@@ -0,0 +1,103 @@ 

+ from __future__ import unicode_literals, absolute_import

+ 

+ import irc.client

+ import flask

+ from .utils import SessionEventLogger

+ 

+ 

+ def check_avail_view():

+     nickname = flask.request.args.get("nickname")

+     if not nickname:

+         return flask.jsonify({

+             "status": "ERROR",

+             "msg": "You must supply a nickname to check."

+         })

+     reactor = irc.client.Reactor()

+     try:

+         server = reactor.server().connect(

+             'irc.freenode.net',

+             6667,

+             nickname)

+     except irc.client.ServerConnectionError:

+         print(sys.exc_info()[1])

+         return flask.jsonify({

+             'status': 'ERROR'

+         })

+     events = SessionEventLogger()

+     server.add_global_handler('nicknameinuse', events.on("nicknameinuse"))

+     server.add_global_handler('welcome', events.on("nickav_connect"))

+     while server.connected:

+         if events.has_happened("nicknameinuse"):

+             events.forget("nicknameinuse")

+             server.close()

+             return flask.jsonify({

+                 'status': 'ERROR',

+                 'msg': 'Nickname in use',

+             })

+         if events.has_happened("nickav_connect"):

+             events.forget("nickav_connect")

+             server.close()

+             return flask.jsonify({

+                 'status': 'OK',

+                 'msg': 'Nickname in available',

+             })

+         reactor.process_once()

+ 

+ 

+ def register_nick_view():

+     request_data = flask.request.get_json()

+     try:

+         nickname = request_data["nickname"]

+         password = request_data["password"]

+         email = request_data["email"]

+     except KeyError as e:

+         return flask.jsonify({

+             "status": "ERROR",

+             "msg": str(e),

+         })

+     reactor = irc.client.Reactor()

+     try:

+         server = reactor.server().connect(

+             'irc.freenode.net',

+             6667,

+             nickname

+         )

+     except irc.client.ServerConnectionError:

+         return flask.jsonify({

+             'status': 'ERROR'

+         })

+ 

+     def register_nick(connection):

+         connection.privmsg(

+             "NickServ",

+             "register {password} {email}".format(

+                 password=password, email=email)

+             )

+         return 'NO_ERROR'

+ 

+     server.add_global_handler('nicknameinuse', events.on("nicknameinuse"))

+     server.add_global_handler(

+         'welcome', events.on("welcome", cb_next=register_nick))

+     # TODO: check the answer from NickServ

+ 

+     while server.connected:

+         if events.has_happened("nicknameinuse"):

+             events.forget("nicknameinuse")

+             server.close()

+             return flask.jsonify({

+                 'status': 'ERROR',

+                 'msg': 'Nickname in use'

+             })

+ 

+         if events.has_happened("welcome"):

+             events.forget("welcome")

+             # TODO: this should actually be a check for a positive answer

+             # from NickServ

+             server.close()

+             return flask.jsonify({

+                 'status': 'OK',

+                 'msg': 'Nickname has been registered',

+             })

+ 

+         reactor.process_once()

+ register_nick_view.methods = ["POST"]

no initial comment

7 new commits added

  • irc: Cosmetic changes to the register nickname modal
  • irc: Return message on the nick is available
  • irc: Create the modal template for the register nickname
  • irc: Add the code for the hubs register nickname
  • irc: Add the first step modal along with suggest nickname
  • hubs.static: Initial to migrate the JS code to their own files
  • hubs.static: Initial to migrate the JS code to their own files
6 years ago

6 new commits added

  • irc: Cosmetic changes to the register nickname modal
  • irc: Return message on the nick is available
  • irc: Create the modal template for the register nickname
  • irc: Add the code for the hubs register nickname
  • irc: Add the first step modal along with suggest nickname
  • hubs.static: Initial to migrate the JS code to their own files
6 years ago

rebased onto fa87e03

6 years ago

is this PR still being worked on? If not, we should close to clean up the queue a bit.

Closing this one off due to inactivity. Feel free to re-open if it is still being worked on.

Pull-Request has been closed by ryanlerch

6 years ago