Browse Source

Port demo chat app from libevent-browserchannel-server.

Andy Hochhaus 5 years ago
parent
commit
a2c859b9b0
6 changed files with 587 additions and 0 deletions
  1. 5 0
      .gitignore
  2. 6 0
      README.md
  3. 188 0
      chat.css
  4. 26 0
      chat.htm
  5. 272 0
      chat.js
  6. 90 0
      wcchat.go

+ 5 - 0
.gitignore

@@ -1,3 +1,8 @@
+*~
+\#*#
+.*.swp
+.DS_Store
+
 # Compiled Object files, Static and Dynamic libs (Shared Objects)
 *.o
 *.a

+ 6 - 0
README.md

@@ -2,3 +2,9 @@ wcchat
 ======
 
 WebChannel chat demo application
+
+git clone https://github.com/google/closure-library.git /path/closure-library
+
+go get github.com/samegoal/wcchat
+cd src/github.com/samegoal/wcchat
+go run wcchat.go --closure=/path/closure-library

+ 188 - 0
chat.css

@@ -0,0 +1,188 @@
+/*
+ * Copyright (c) 2014 SameGoal LLC. All Rights Reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Portions of this code are from The Closure Library Authors, received by
+ * SameGoal under the Apache license, Version 2.0. All other code is
+ * Copyright (c) 2014 SameGoal, LLC.
+ */
+
+body {
+  margin: 0;
+  padding: 0;
+}
+
+html>body .goog-inline-block {
+  display: -moz-inline-box;
+  display: inline-block;
+}
+
+.goog-inline-block {
+  position: relative;
+  display: -moz-inline-box;
+  display: inline-block;
+}
+
+* html .goog-inline-block {
+  display: inline;
+}
+
+*:first-child+html .goog-inline-block {
+  display: inline;
+}
+
+.goog-splitpane {}
+
+.goog-splitpane-handle {
+  background: #ccc;
+  position: absolute;
+}
+
+.goog-splitpane-handle-horizontal {
+  cursor: col-resize;
+}
+
+.goog-splitpane-first-container, .goog-splitpane-second-container {
+  overflow: auto;
+  position: absolute;
+}
+
+.modal-dialog-bg {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  background-color: #FFF;
+}
+
+.modal-dialog {
+  position: absolute;
+  top: 0px;
+  left: 0px;
+  width: 300px;
+  background-color: #AAF;
+  border: 2px solid #99c0ff;
+}
+
+.modal-dialog-title {
+  position:relative;
+  background: #C3D9FF;
+  padding: 4px;
+  font: bold 11px verdana;
+  cursor: default;
+}
+
+.modal-dialog-content {
+  background: #E8EEF7;
+  padding: 12px 18px 12px 18px;
+  font: normal 12px verdana;
+}
+
+.modal-dialog-user-input {
+  font: normal 12px verdana;
+  width: 90%;
+}
+
+.modal-dialog-buttons {
+  background: #E8EEF7;
+  padding: 4px;
+  font: normal 12px verdana;
+  text-align: right;
+}
+
+.modal-dialog-buttons button {
+  margin: 5px;
+}
+
+.goog-control {
+  position: relative;
+  margin: 2px;
+  border: 2px solid #036;
+  padding: 2px;
+  font: normal 9pt "Trebuchet MS", Tahoma, Arial, sans-serif;
+  color: #036;
+  background-color:#69c;
+  cursor: pointer;
+  outline: none !important;
+}
+
+.goog-control-disabled {
+  border-color: #888;
+  color: #888;
+  background-color: #ccc;
+}
+
+.goog-control-hover {
+  border-color: #369;
+  color: #369;
+  background-color: #9cf;
+}
+
+.goog-control-active,
+.goog-control-selected,
+.goog-control-checked {
+  border-color: #9cf;
+  color: #9cf;
+  background-color: #369;
+}
+
+.goog-control-focused {
+  border-color: orange;
+}
+
+.goog-container {
+  position: relative;
+  margin: 0;
+  border: 0;
+  padding: 0;
+  background-color: #eee;
+  outline: none !important;
+  zoom: 1;
+}
+
+.goog-container-vertical {
+  width: 25ex;
+  border: 1px solid #888;
+}
+
+textarea {
+  width: 90%;
+  padding: 2px;
+}
+
+.goog-flat-button {
+  position: relative;
+  margin: 2px;
+  border: 1px solid #000;
+  padding: 2px 6px;
+  font: normal 13px "Trebuchet MS", Tahoma, Arial, sans-serif;
+  color: #fff;
+  background-color: #8c2425;
+  cursor: pointer;
+  outline: none;
+}
+
+.goog-flat-button-disabled {
+  border-color: #888;
+  color: #888;
+  background-color: #ccc;
+  cursor: default;
+}
+
+.goog-flat-button-hover {
+  border-color: #8c2425;
+  color: #8c2425;
+  background-color: #eaa4a5;
+}
+
+.goog-flat-button-active,
+.goog-flat-button-selected,
+.goog-flat-button-checked {
+  border-color: #5b4169;
+  color: #5b4169;
+  background-color: #d1a8ea;
+}
+
+.goog-flat-button-focused {
+  border-color: #5b4169;
+}

+ 26 - 0
chat.htm

@@ -0,0 +1,26 @@
+<!--
+  Copyright (c) 2014 SameGoal LLC. All Rights Reserved.
+  Use of this source code is governed by a BSD-style license that can be
+  found in the LICENSE file.
+-->
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <title>Chat Demo</title>
+    <meta charset="utf-8">
+    <link rel="stylesheet" href="/chat.css">
+  </head>
+  <body>
+    <script src="/closure/goog/base.js"></script>
+    <script>
+      goog.require('goog.dom.ViewportSizeMonitor');
+      goog.require('goog.net.BrowserChannel');
+      goog.require('goog.ui.Container');
+      goog.require('goog.ui.FlatButtonRenderer');
+      goog.require('goog.ui.Prompt');
+      goog.require('goog.ui.SplitPane');
+      goog.require('goog.ui.Textarea');
+    </script>
+    <script type="text/javascript" src="/chat.js"></script>
+  </body>
+</html>

+ 272 - 0
chat.js

@@ -0,0 +1,272 @@
+/**
+ * Copyright (c) 2014 SameGoal LLC. All Rights Reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Portions of this code are from The Closure Library Authors, received by
+ * SameGoal under the Apache license, Version 2.0. All other code is
+ * Copyright (c) 2014 SameGoal, LLC.
+ */
+
+
+/**
+ * @fileoverview Demo goog.net.WebChannel chat application.
+ */
+
+goog.provide('wcchat.App');
+goog.provide('wcchat.ChannelHandler');
+
+goog.require('goog.Disposable');
+goog.require('goog.Timer');
+goog.require('goog.Uri');
+goog.require('goog.dom');
+goog.require('goog.dom.ViewportSizeMonitor');
+goog.require('goog.events');
+goog.require('goog.events.EventType');
+goog.require('goog.json');
+goog.require('goog.net.BrowserChannel');
+goog.require('goog.net.BrowserChannel.Handler');
+goog.require('goog.net.ChannelRequest');
+goog.require('goog.net.tmpnetwork');
+goog.require('goog.string.StringBuffer');
+goog.require('goog.structs.Map');
+goog.require('goog.ui.Button');
+goog.require('goog.ui.Component');
+goog.require('goog.ui.Component.EventType');
+goog.require('goog.ui.Container');
+goog.require('goog.ui.Control');
+goog.require('goog.ui.FlatButtonRenderer');
+goog.require('goog.ui.Prompt');
+goog.require('goog.ui.SplitPane');
+goog.require('goog.ui.Textarea');
+
+
+/**
+ * @define {boolean} DEBUG displays the debugging window when using the chat
+ * application.
+ */
+wcchat.DEBUG = false;
+
+
+/**
+ * Browser channel handler.
+ * @constructor
+ * @extends {goog.net.BrowserChannel.Handler}
+ */
+wcchat.ChannelHandler = function() {
+};
+goog.inherits(wcchat.ChannelHandler, goog.net.BrowserChannel.Handler);
+
+
+/** @override */
+wcchat.ChannelHandler.prototype.channelOpened = function(browserChannel) {
+  app.msg('**channelOpened');
+};
+
+
+/** @override */
+wcchat.ChannelHandler.prototype.channelError = function(
+    browserChannel, error) {
+  app.msg('**channelError');
+};
+
+
+/** @override */
+wcchat.ChannelHandler.prototype.channelClosed =
+    function(browserChannel, opt_pendingMaps, opt_undeliveredMaps) {
+  app.msg('**channelClosed');
+};
+
+
+/** @override */
+wcchat.ChannelHandler.prototype.channelHandleArray =
+    function(browserChannel, array) {
+  var cmd = array[0];
+  var item = array[1];
+  if (cmd == 'new') {
+    var cont = new goog.ui.Control(item);
+    cont.setId(array[2]);
+    app.user_list.addChild(cont, true);
+  } else if (cmd == 'message') {
+    app.msg('<b>' + item + '</b>: ' + array[2]);
+  } else if (cmd == 'delete') {
+    app.user_list.removeChild(item, true);
+  }
+};
+
+
+/** @override */
+wcchat.ChannelHandler.prototype.getNetworkTestImageUri =
+    function(browserChannel) {
+  app.msg('**getNetworkTestImageUri');
+  return new goog.Uri('/closure/goog/labs/net/testdata/cleardot.gif');
+};
+
+
+
+
+/**
+ * Chat demo application.
+ * @constructor
+ * @extends {goog.Disposable}
+ */
+wcchat.App = function() {
+  goog.base(this);
+};
+goog.inherits(wcchat.App, goog.Disposable);
+
+wcchat.App.prototype.launch = function() {
+  if (wcchat.DEBUG) {
+    this.debugWindow = new goog.debug.FancyWindow('debugWindow');
+    this.debugWindow.setEnabled(true);
+    this.debugWindow.init();
+  }
+
+  /**
+   * @type {goog.ui.Container}
+   * @private
+   */
+  this.userList_ = new goog.ui.Container();
+
+
+  /**
+   * @type {goog.ui.Component}
+   * @private
+   */
+  this.chatUi_ = new goog.ui.Component();
+
+
+  /**
+   * @type {goog.ui.Textarea}
+   * @private
+   */
+  this.textArea_ = new goog.ui.Textarea('');
+  this.textArea_.setMinHeight(50);
+  this.textArea_.setMaxHeight(100);
+  this.chatUi_.addChild(this.textArea_, true);
+
+
+  /**
+   * @type {goog.ui.Button}
+   * @private
+   */
+  this.sendButton_ = new goog.ui.Button(
+    'Send', goog.ui.FlatButtonRenderer.getInstance());
+  this.chatUi_.addChild(this.sendButton_, true);
+
+
+  /**
+   * @type {goog.ui.Component}
+   * @private
+   */
+  this.chatConsole_ = new goog.ui.Component();
+  this.chatUi_.addChild(this.chatConsole_, true);
+
+
+  /**
+   * @type {goog.ui.SplitPane}
+   * @private
+   */
+  this.splitpane_ = new goog.ui.SplitPane(
+      this.userList_, this.chatUi_, goog.ui.SplitPane.Orientation.HORIZONTAL);
+  this.splitpane_.setInitialSize(175);
+  this.splitpane_.render();
+
+
+  this.userList_.getElement().innerHTML +=
+  '<a href=\"#\" onclick=\"disconnect(); return false;\">disconnect</a>';
+  this.chatConsole_.getElement().innerHTML +=
+  '<div id=\"chatPane\" width=\"100%\"></div>';
+  goog.events.listen(this.sendButton_,
+                     goog.ui.Component.EventType.ACTION,
+                     wcchat.send);
+
+
+  /**
+   * @type {goog.dom.ViewportSizeMonitor}
+   * @private
+   */
+  this.viewportSizeMonitor_ = new goog.dom.ViewportSizeMonitor();
+  goog.events.listen(this.viewportSizeMonitor_,
+                     goog.events.EventType.RESIZE,
+                     this.resizeCallback_,
+                     false,
+                     this);
+  this.resizeCallback_();
+
+
+  /**
+   * @type {goog.net.BrowserChannel}
+   * @private
+   */
+  this.channel_ = new goog.net.BrowserChannel('8' ['', '']);
+  // if using CORS host prefix call: channel.setSupportsCrossDomainXhrs(true);
+  this.channel_.setHandler(new wcchat.ChannelHandler());
+  if (wcchat.DEBUG) {
+    this.channel_.setChannelDebug(new goog.net.ChannelDebug());
+  }
+  this.channel_.connect('channel/test', 'channel/bind', {});
+};
+
+
+/**
+ * Callback to resize split pane when viewport is resized by the user.
+ * @param {goog.dom.ViewportSizeMonitor=} opt_event Event.
+ * @private
+ */
+wcchat.App.prototype.resizeCallback_ = function(opt_event) {
+  this.splitpane_.setSize(this.viewportSizeMonitor_.getSize());
+};
+
+
+/**
+ * Log message to the console.
+ * @param {string} message String to log.
+ */
+wcchat.App.prototype.msg = function(message) {
+  var msgDiv = goog.dom.createDom('DIV', undefined,
+      goog.dom.htmlToDocumentFragment(message));
+  var el = /** @type {!Element} */ (goog.dom.getElement('chatPane'));
+  goog.dom.append(el, msgDiv);
+  el.scrollTop = el.scrollHeight;
+};
+
+
+
+/**
+ * Send a chat message.
+ */
+wcchat.send = function() {
+  var text = goog.string.htmlEscape(app.textArea_.getValue());
+  app.textArea_.setValue('');
+  channel.sendMap({'message': text});
+};
+
+
+/**
+ * Set username.
+ * @param {string} response User prompt response.
+ */
+wcchat.send_username = function(response) {
+  channel.sendMap({'user': response ? response : 'Anonmous'});
+};
+
+var p = new goog.ui.Prompt(
+    'Username Required', 'What is your name?', wcchat.send_username);
+p.setVisible(true);
+
+
+/**
+ * Disconnect browserchannel.
+ */
+wcchat.disconnect = function() {
+  wcchat.user_list.dispose();
+  channel.disconnect();
+};
+
+/**
+ * @type {wcchat.App}
+ */
+var app = new wcchat.App();
+goog.exportSymbol('disconnect', app.disconnect);
+app.launch();

+ 90 - 0
wcchat.go

@@ -0,0 +1,90 @@
+// Copyright (c) 2014 SameGoal LLC. All Rights Reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"mime"
+	"net/http"
+	"path"
+	"path/filepath"
+	"strings"
+
+	"github.com/samegoal/wc"
+)
+
+var (
+	closurePath = flag.String("closure", "", "closure-library path.")
+)
+
+type appHandler func(http.ResponseWriter, *http.Request) error
+
+func (fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	if err := fn(w, r); err != nil {
+		http.Error(w, err.Error(), 500)
+	}
+}
+
+func fromDisk(w http.ResponseWriter, p string) error {
+	b, err := ioutil.ReadFile(p)
+	if err != nil {
+		return err
+	}
+	m := mime.TypeByExtension(path.Ext(p))
+	if m == "" {
+		return fmt.Errorf("Unable to find mime type")
+	}
+	w.Header().Set("Content-Type", m)
+	_, err = w.Write(b)
+	return err
+}
+
+func chatHTMLHandler(w http.ResponseWriter, r *http.Request) error {
+	if r.URL.Path != "/" {
+		http.NotFound(w, r)
+		return nil
+	}
+	return fromDisk(w, "chat.htm")
+}
+
+func chatCSSHandler(w http.ResponseWriter, r *http.Request) error {
+	return fromDisk(w, "chat.css")
+}
+
+func chatJSHandler(w http.ResponseWriter, r *http.Request) error {
+	return fromDisk(w, "chat.js")
+}
+
+func closureHandler(w http.ResponseWriter, r *http.Request) error {
+	requestedPath := path.Join(*closurePath, r.URL.Path)
+	if !strings.HasPrefix(requestedPath, *closurePath) {
+		return fmt.Errorf("Illegal requested file path.")
+	}
+	return fromDisk(w, requestedPath)
+}
+
+func main() {
+	flag.Parse()
+
+	var err error
+	*closurePath, err = filepath.Abs(*closurePath)
+	if err != nil {
+		panic("Illegal --closure path.")
+	}
+
+	// application resource handlers
+	http.Handle("/", appHandler(chatHTMLHandler))
+	http.Handle("/chat.css", appHandler(chatCSSHandler))
+	http.Handle("/chat.js", appHandler(chatJSHandler))
+	http.Handle("/closure/", appHandler(closureHandler))
+
+	// WebChannel handlers
+	http.HandleFunc("/channel/test", wc.TestHandler)
+	http.HandleFunc("/channel/bind", wc.BindHandler)
+
+	panic(http.ListenAndServe(":8080", nil))
+}