NodeWebkit、Socket.io、およびMEANを使用してリアルタイムのチャットルームを作成する
序章
開発担当者は、プログラムの構築を可能な限り簡単にするためにたゆまぬ努力をしています。 NodeとCordovaが導入されて以来、JavaScript、Web、およびモバイルアプリの開発者コミュニティは劇的に増加しました。 Webデザインのスキルを持っている開発者は、Node.jsの助けを借りて、少ない労力で、アプリケーションにJavaScriptを使用してサーバーを展開できます。
モバイル愛好家は、Cordovaの助けを借りて、JavaScriptだけを使用してリッチなハイブリッドアプリを構築できるようになりました。 今日、それは古いニュースですが、JavaScriptを使用してデスクトップスタンドアロンアプリケーションを構築する機能を共有できることに興奮しています。
Node WebKit 通常の記述:「node-webkit」または「NW.js」は、Node.jsとChromiumに基づくアプリランタイムであり、HTML、CSS、JavaScriptのみを使用してOSネイティブアプリを開発できます。
簡単に言えば、Node WebKitは、Web開発者としてのスキルを活用して、Mac、Windows、およびLinuxでgrunt / gulp(必要に応じて)ビルドコマンドだけで快適に実行されるネイティブアプリケーションを構築するのに役立ちます。
この記事では、Node WebKitの使用に重点を置いていますが、物事をより面白くするために、他のすばらしいソリューションを追加します。
- Socket.ioNode.js用のリアルタイムライブラリ
- Angular Material :AngularによるGoogleのマテリアルデザインの実装
- MEAN:MEANは、Mongo、Express、Angular、Nodeの機能を組み合わせて強力なアプリを構築するという概念にすぎません。
さらに、アプリケーションには3つのセクションがあります。
- サーバー
- デスクトップ(クライアント)
- ウェブ(クライアント)
ここではWebセクションについては説明しませんが、テストプラットフォームとして機能しますが、コードが提供されますのでご安心ください。
前提条件
レベル:中級( MEAN の知識が必要です)
インストール
私たちはつかむ必要があります node-webkit
およびアプリケーションの他の依存関係。 幸い、ワークフローを簡単にするフレームワークがあり、そのうちの1つを使用してアプリケーションの足場を作り、実装にさらに集中します。
YoとSlushは人気のあるジェネレーターであり、これらのいずれも機能します。 Slush を使用しますが、必要に応じてYoを使用してください。 Slushをインストールするには、ノードとnpmがインストールされて実行されていることを確認してください
- npm install -g slush gulp bower slush-wean
このコマンドは、次のものをシステムにグローバルにインストールします。
YOと同じように、次を使用してディレクトリとスキャフォールドをアプリにします。
- mkdir scotch-chat
- cd scotch-chat
- slush wean
以下のコマンドを実行すると、私たちが待ち望んでいたものが一目でわかります。
- gulp run
画像はアプリの読み込みを示しています。 ジェネレーターの作成者は、単純な読み込みアニメーションを備えた素敵なテンプレートを提供するのに十分寛大でした。 見栄えを良くするために、読み込み中のテキストをScotchのロゴに置き換えました。
Slushの自動化に慣れていない場合は、GitHubのNodeWebKitに直接アクセスできます。
アプリをセットアップしたので、空ですが、休憩を取り、サーバーを準備します。
サーバー
サーバーは基本的に、モデル、ルート、およびソケットイベントで構成されています。 可能な限りシンプルに保ち、記事の最後にある指示に従ってアプリを自由に拡張できます。
ディレクトリ構造
PCのお気に入りのディレクトリにフォルダを設定しますが、フォルダの内容が次のようになっていることを確認してください。
|- public
|- index.html
|- server.js
|- package.json
依存関係
の中に package.json
ルートディレクトリにあるファイルで、アプリケーションを記述し、アプリケーションの依存関係を含めるJSONファイルを作成します。
{
"name": "scotch-chat",
"main": "server.js",
"dependencies": {
"mongoose": "latest",
"morgan": "latest",
"socket.io": "latest"
}
}
それで十分です。 これは最小限の設定であり、シンプルで短くしています。 走る npm install
ディレクトリルートで、指定された依存関係をインストールします。
- npm install
サーバーセットアップの開始
手を汚す時が来ました! まず、でグローバル変数を設定します server.js
これは、すでにインストールされているアプリケーションの依存関係を保持します。
// Import all our dependencies
var express = require('express');
var mongoose = require('mongoose');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
わかりました、私は私の言葉を守りませんでした。 変数は依存関係を保持しているだけでなく、使用できるように構成しているものもあります。
静的ファイルを提供するために、expressは静的ファイルフォルダーの構成に役立つメソッドを公開します。 簡単です:
...
// tell express where to serve static files from
app.use(express.static(__dirname + '/public'));
次は、データベースへの接続を作成します。 私はローカルのMongoDBを使用していますが、これはMongoデータベースによってホストされていることがわかるため、明らかにオプションです。 Mongoose は、MongoDBでの作業をはるかに簡単にする素晴らしいAPIを公開するノードモジュールです。
...
mongoose.connect("mongodb://127.0.0.1:27017/scotch-chat");
Mongooseを使用して、データベーススキーマとモデルを作成できるようになりました。 また、別のドメインからアクセスするため、アプリケーションでCORSを許可する必要があります。
...
// create a schema for chat
var ChatSchema = mongoose.Schema({
created: Date,
content: String,
username: String,
room: String
});
// create a model from the chat schema
var Chat = mongoose.model('Chat', ChatSchema);
// allow CORS
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-type,Accept,X-Access-Token,X-Key');
if (req.method == 'OPTIONS') {
res.status(200).end();
} else {
next();
}
});
サーバーには3つのルートがあります。 インデックスファイルを提供するルート、チャットデータを設定するルート、および部屋名でフィルタリングされたチャットメッセージを提供する最後のルート:
/*||||||||||||||||||||||ROUTES|||||||||||||||||||||||||*/
// route for our index file
app.get('/', function(req, res) {
//send the index.html in our public directory
res.sendfile('index.html');
});
//This route is simply run only on first launch just to generate some chat history
app.post('/setup', function(req, res) {
//Array of chat data. Each object properties must match the schema object properties
var chatData = [{
created: new Date(),
content: 'Hi',
username: 'Chris',
room: 'php'
}, {
created: new Date(),
content: 'Hello',
username: 'Obinna',
room: 'laravel'
}, {
created: new Date(),
content: 'Ait',
username: 'Bill',
room: 'angular'
}, {
created: new Date(),
content: 'Amazing room',
username: 'Patience',
room: 'socet.io'
}];
//Loop through each of the chat data and insert into the database
for (var c = 0; c < chatData.length; c++) {
//Create an instance of the chat model
var newChat = new Chat(chatData[c]);
//Call save to insert the chat
newChat.save(function(err, savedChat) {
console.log(savedChat);
});
}
//Send a resoponse so the serve would not get stuck
res.send('created');
});
//This route produces a list of chat as filterd by 'room' query
app.get('/msg', function(req, res) {
//Find
Chat.find({
'room': req.query.room.toLowerCase()
}).exec(function(err, msgs) {
//Send
res.json(msgs);
});
});
/*||||||||||||||||||END ROUTES|||||||||||||||||||||*/
私が信じる最初のルートは十分に簡単です。 それは私たちを送るだけです index.html
ユーザーにファイルします。
二番目 /setup
これは、アプリケーションの最初の起動時に1回だけヒットすることを目的としています。 テストデータが必要ない場合はオプションです。 基本的に、チャットメッセージの配列(スキーマに一致する)を作成し、それらをループして、データベースに挿入します。
3番目のルート /msg
部屋名でフィルタリングされ、JSONオブジェクトの配列として返されるチャット履歴をフェッチする責任があります。
サーバーの最も重要な部分は、リアルタイムロジックです。 単純なアプリケーションの作成に取り組んでいることを念頭に置いて、ロジックは包括的に最小限に抑えられます。 続いて、次のことを行う必要があります。
- アプリケーションがいつ起動されるかを知る
- 接続時に利用可能なすべての部屋を送信する
- ユーザーが接続するのを聞いて、デフォルトの部屋に割り当てます
- 彼らが部屋を切り替えるときに耳を傾ける
- そして最後に、新しいメッセージを聞いて、それが作成された部屋の人にのみメッセージを送信します
したがって:
/*||||||||||||||||SOCKET|||||||||||||||||||||||*/
//Listen for connection
io.on('connection', function(socket) {
//Globals
var defaultRoom = 'general';
var rooms = ["General", "angular", "socket.io", "express", "node", "mongo", "PHP", "laravel"];
//Emit the rooms array
socket.emit('setup', {
rooms: rooms
});
//Listens for new user
socket.on('new user', function(data) {
data.room = defaultRoom;
//New user joins the default room
socket.join(defaultRoom);
//Tell all those in the room that a new user joined
io.in(defaultRoom).emit('user joined', data);
});
//Listens for switch room
socket.on('switch room', function(data) {
//Handles joining and leaving rooms
//console.log(data);
socket.leave(data.oldRoom);
socket.join(data.newRoom);
io.in(data.oldRoom).emit('user left', data);
io.in(data.newRoom).emit('user joined', data);
});
//Listens for a new chat message
socket.on('new message', function(data) {
//Create message
var newMsg = new Chat({
username: data.username,
content: data.message,
room: data.room.toLowerCase(),
created: new Date()
});
//Save it to database
newMsg.save(function(err, msg){
//Send message to those connected in the room
io.in(msg.room).emit('message created', msg);
});
});
});
/*||||||||||||||||||||END SOCKETS||||||||||||||||||*/
次に、従来のサーバーが起動します。
server.listen(2015);
console.log('It\'s going down in 2015');
記入する index.html
自分に合ったHTMLを使用して実行 node server.js
. localhost:2015
HTMLのコンテンツを提供します。
ノードWebKitクライアント
現在実行中のサーバーを作成するために残したものを掘り下げる時間です。 このセクションは、HTML、CSS、JS、およびAngularの日常的な知識が必要なだけなので非常に簡単です。
–>
ディレクトリ構造
作成する必要はありません! それが発電機のインスピレーションだったと思います。 あなたが調べたいと思うかもしれない最初のファイルは package.json
.
Node WebKitを実行するには、基本的に2つの主要なファイルが必要です。
- エントリポイント(
index.html
) - a
package.json
エントリポイントがどこにあるかを通知します
package.json
そのメインがの場所であることを除いて、私たちが慣れている基本的なコンテンツを持っています index.html
、および以下の一連の構成があります "window":
ここから、アイコン、サイズ、ツールバー、フレームなど、アプリのウィンドウのすべてのプロパティを定義します。
依存関係
サーバーとは異なり、bowerを使用して依存関係をロードします。これは、クライアントアプリケーションであるためです。 更新する bower.json
依存関係:
"dependencies": {
"angular": "^1.3.13",
"angular-material" : "^0.10.0",
"angular-socket-io" : "^0.7.0",
"angular-material-icons":"^0.5.0",
"animate.css":"^3.0.0"
}
ショートカットの場合は、次のコマンドを実行するだけです。
- bower install --save angular angular-material angular-socket-io angular-material-icons animate.css
フロントエンドの依存関係ができたので、更新できます views/index.ejs
に:
<html><head>
<title>scotch-chat</title>
<link rel="stylesheet" href="css/app.css">
<link rel="stylesheet" href="css/animate.css">
<link rel="stylesheet" href="libs/angular-material/angular-material.css">
<script src="libs/angular/angular.js"></script>
<script src="http://localhost:2015/socket.io/socket.io.js"></script>
<script type="text/javascript" src="libs/angular-animate/angular-animate.js"></script>
<script type="text/javascript" src="libs/angular-aria/angular-aria.js"></script>
<script type="text/javascript" src="libs/angular-material/angular-material.js"></script>
<script type="text/javascript" src="libs/angular-socket-io/socket.js"></script>
<script type="text/javascript" src="libs/angular-material-icons/angular-material-icons.js"></script>
<script src="js/app.js"></script>
</head>
<body ng-controller="MainCtrl" ng-init="usernameModal()">
<md-content>
<section>
<md-list>
<md-subheader class="md-primary header">Room: {{room}} <span align="right">Userame: {{username}} </span> </md-subheader>
<md-whiteframe ng-repeat="m in messages" class="md-whiteframe-z2 message" layout layout-align="center center">
<md-list-item class="md-3-line">
<img ng-src="img/user.png" class="md-avatar" alt="User" />
<div class="md-list-item-text">
<h3>{{ m.username }}</h3>
<p>{{m.content}}</p>
</div>
</md-list-item>
</md-whiteframe>
</md-list>
</section>
<div class="footer">
<md-input-container>
<label>Message</label>
<textarea ng-model="message" columns="1" md-maxlength="100" ng-enter="send(message)"></textarea>
</md-input-container>
</div>
</md-content>
</body>
</html>
すべての依存関係とカスタムファイル(app.cssとapp.js)を含めました。 注意事項:
- 角度のある素材を使用しており、そのディレクティブによってコードが「HTML6」のように見えます。
- を使用してメッセージスコープをループしています
ng-repeat
そしてその値をブラウザにレンダリングします - 後で見るディレクティブは、次の場合にメッセージを送信するのに役立ちます。
ENTER
キーが押された - の上
init
、ユーザーは優先ユーザー名を求められます - AngularでSocket.ioを簡単に操作できるように含まれているAngularライブラリがあります。
アプリケーション
このセクションの主要部分は app.js
ファイル。 これは、Node WebKit GUIと対話するためのサービスを作成します。これは、 ENTER
キーを押すとコントローラー(メインとダイアログ)。
//Load angular
var app = angular.module('scotch-chat', ['ngMaterial', 'ngAnimate', 'ngMdIcons', 'btford.socket-io']);
//Set our server url
var serverBaseUrl = 'http://localhost:2015';
//Services to interact with nodewebkit GUI and Window
app.factory('GUI', function () {
//Return nw.gui
return require('nw.gui');
});
app.factory('Window', function (GUI) {
return GUI.Window.get();
});
//Service to interact with the socket library
app.factory('socket', function (socketFactory) {
var myIoSocket = io.connect(serverBaseUrl);
var socket = socketFactory({
ioSocket: myIoSocket
});
return socket;
});
次に、3つのAngularサービスを作成します。 最初のサービスはそのNodeWebKitGUIオブジェクトを取得するのに役立ち、2番目のサービスはそのWindowプロパティを返し、3番目のサービスはベースURLを使用してSocket.ioをブートストラップします。
//ng-enter directive
app.directive('ngEnter', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if (event.which === 13) {
scope.$apply(function () {
scope.$eval(attrs.ngEnter);
});
event.preventDefault();
}
});
};
});
上記のスニペットは、Angularを使用して以来、私のお気に入りの1つです。 イベントをにバインドします ENTER
キー。これにより、キーが押されたときにイベントをトリガーできます。
最後に、 app.js
全能のコントローラーです。 理解を容易にするために、物事を分解する必要があります。 server.js
. コントローラは次のことを期待されています。
- サーバーから放出された部屋を使用して、ウィンドウメニューのリストを作成します。
- 参加するユーザーは、ユーザー名を提供する必要があります。
- サーバーからの新しいメッセージをリッスンします。
- 新しいメッセージが作成されたら、次のように入力してサーバーに通知します。
ENTER
鍵。
部屋のリストを作成する
目標を定義したら、次のようにコーディングします。
//Our Controller
app.controller('MainCtrl', function ($scope, Window, GUI, $mdDialog, socket, $http){
//Menu setup
//Modal setup
//listen for new message
//Notify server of the new message
});
これが、すべての依存関係を持つコントローラーのスケルトンです。 ご覧のとおり、目標で定義されているコードのプレースホルダーとして機能する4つの内部コメントがあります。 それでは、メニューを選びましょう。
//Global Scope
$scope.messages = [];
$scope.room = "";
//Build the window menu for our app using the GUI and Window service
var windowMenu = new GUI.Menu({
type: 'menubar'
});
var roomsMenu = new GUI.Menu();
windowMenu.append(new GUI.MenuItem({
label: 'Rooms',
submenu: roomsMenu
}));
windowMenu.append(new GUI.MenuItem({
label: 'Exit',
click: function () {
Window.close()
}
}));
メニューのインスタンスを作成し、それにいくつかのメニュー(RoomsとExit)を追加しただけです。 部屋メニューはドロップダウンとして機能することが期待されているため、サーバーに利用可能な部屋を要求し、それを部屋メニューに追加する必要があります。
//Listen for the setup event and create rooms
socket.on('setup', function (data) {
var rooms = data.rooms;
for (var r = 0; r < rooms.length; r++) {
//Loop and append room to the window room menu
handleRoomSubMenu(r);
}
//Handle creation of room
function handleRoomSubMenu(r) {
var clickedRoom = rooms[r];
//Append each room to the menu
roomsMenu.append(new GUI.MenuItem({
label: clickedRoom.toUpperCase(),
click: function () {
//What happens on clicking the rooms? Swtich room.
$scope.room = clickedRoom.toUpperCase();
//Notify the server that the user changed his room
socket.emit('switch room', {
newRoom: clickedRoom,
username: $scope.username
});
//Fetch the new rooms messages
$http.get(serverBaseUrl + '/msg?room=' + clickedRoom).success(function (msgs) {
$scope.messages = msgs;
});
}
}));
}
//Attach menu
GUI.Window.get().menu = windowMenu;
});
上記のコードは、関数を使用して、サーバーから利用可能な部屋の配列をループし、部屋のメニューに追加します。 これで、目的#1は完了です。
ユーザー名を尋ねる
2番目の目的は、角度のあるマテリアルモーダルを使用してユーザーにユーザー名を尋ねることです。
$scope.usernameModal = function (ev) {
//Launch Modal to get username
$mdDialog.show({
controller: UsernameDialogController,
templateUrl: 'partials/username.tmpl.html',
parent: angular.element(document.body),
targetEvent: ev,
})
.then(function (answer) {
//Set username with the value returned from the modal
$scope.username = answer;
//Tell the server there is a new user
socket.emit('new user', {
username: answer
});
//Set room to general;
$scope.room = 'GENERAL';
//Fetch chat messages in GENERAL
$http.get(serverBaseUrl + '/msg?room=' + $scope.room).success(function (msgs) {
$scope.messages = msgs;
});
}, function () {
Window.close();
});
};
HTMLで指定されているように、initでは、 usernameModal
と呼ばれます。 それは使用します mdDialog
参加しているユーザーのユーザー名を取得するサービス。これが成功すると、入力したユーザー名がバインディングスコープに割り当てられ、そのアクティビティについてサーバーに通知してから、ユーザーをデフォルト(GENERAL)ルームにプッシュします。 成功しなかった場合は、アプリを閉じます。 目標#2が完了しました!
//Listen for new messages (Objective 3)
socket.on('message created', function (data) {
//Push to new message to our $scope.messages
$scope.messages.push(data);
//Empty the textarea
$scope.message = "";
});
//Send a new message (Objective 4)
$scope.send = function (msg) {
//Notify the server that there is a new message with the message as packet
socket.emit('new message', {
room: $scope.room,
message: msg,
username: $scope.username
});
};
メッセージを聞く
3番目で最後の目的は単純です。 #3はメッセージをリッスンし、存在する場合は既存のメッセージの配列にプッシュし、#4は新しいメッセージが作成されたときにサーバーに通知します。 の終わりに app.js
、モーダルのコントローラーとして機能する関数を作成します。
//Dialog controller
function UsernameDialogController($scope, $mdDialog) {
$scope.answer = function (answer) {
$mdDialog.hide(answer);
};
}
CSSとアニメーション
いくつかの醜い外観を修正するには、 app.css
.
body {
background: #fafafa !important;
}
.footer {
background: #fff;
position: fixed;
left: 0px;
bottom: 0px;
width: 100%;
}
.message.ng-enter {
-webkit-animation: zoomIn 1s;
-ms-animation: zoomIn 1s;
animation: zoomIn 1s;
}
最後のスタイルに注意してください。 使用しています ngAnimate
と animate.css
メッセージのきれいなアニメーションを作成します。
このコンセプトここでどのように遊ぶことができるかについてはすでに書きました。
締めくくり
画像を見れば何が気になるのかわかります! アドレスバーですね。 これは、 window
の構成 package.json
入って来る。 変更するだけ "toolbar": true
に "toolbar": false
.
アイコンも "icon": "app/public/img/scotch.png"
ウィンドウアイコンをスコッチロゴに変更します。 新しいメッセージが届いたら、通知を追加することもできます。
var options = {
body: data.content
};
var notification = new Notification("Message from: "+data.username, options);
notification.onshow = function () {
// auto close after 1 second
setTimeout(function () {
notification.close();
}, 2000);
}
そしてさらに楽しい…
テスト
GitHubからWebクライアントをダウンロードしてアプリケーションをテストすることをお勧めします。 サーバー、Webクライアント、アプリの順に実行します。 アプリとWebクライアントの両方からメッセージの送信を開始し、同じ部屋でメッセージを送信している場合は、それらがリアルタイムで表示されるのを確認します。
さらに進む
さらに挑戦したい場合は、アプリに以下を追加してみてください
- Facebookによる認証。
- 部屋を更新するための管理セクション。
- 実際のユーザーアバターを使用します。
- を使用してアプリをデプロイする
gulp deploy --{{platform}}
例えば:gulp deploy --mac
. *など…
結論
最後までやり遂げてよかったです。 NodeWebKitは素晴らしいコンセプトです。 コミュニティに参加して、アプリの作成を簡単にします。 今日はスコッチがたくさんあり、誰かを笑顔にしたことを願っています…