From fb0cb184ca8ef999005cb2fc53fbb6938a2069b0 Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Tue, 4 Dec 2018 13:12:39 -0800 Subject: [PATCH 1/2] Websockets sample + add tests --- appengine/websockets/README.md | 54 +++++++++++++++++++++++ appengine/websockets/app.js | 42 ++++++++++++++++++ appengine/websockets/app.yaml | 27 ++++++++++++ appengine/websockets/package.json | 37 ++++++++++++++++ appengine/websockets/test/index.test.js | 46 ++++++++++++++++++++ appengine/websockets/views/index.pug | 58 +++++++++++++++++++++++++ 6 files changed, 264 insertions(+) create mode 100644 appengine/websockets/README.md create mode 100644 appengine/websockets/app.js create mode 100644 appengine/websockets/app.yaml create mode 100644 appengine/websockets/package.json create mode 100644 appengine/websockets/test/index.test.js create mode 100644 appengine/websockets/views/index.pug diff --git a/appengine/websockets/README.md b/appengine/websockets/README.md new file mode 100644 index 0000000000..d2cb521648 --- /dev/null +++ b/appengine/websockets/README.md @@ -0,0 +1,54 @@ +# Node.js websockets sample for Google App Engine + +This sample demonstrates how to use websockets on +[Google App Engine Flexible Environment][appengine] with Node.js. + +* [Setup](#setup) +* [Running locally](#running-locally) +* [Deploying to App Engine](#deploying-to-app-engine) +* [Running the tests](#running-the-tests) + +## Setup + +Before you can run or deploy the sample, you need to do the following: + +1. Refer to the [appengine/README.md][readme] file for instructions on + running and deploying. +1. Install dependencies: + + With `npm`: + + npm install + + or with `yarn`: + + yarn install + +## Running locally + +With `npm`: + + npm start + +or with `yarn`: + + yarn start + +## Deploying to App Engine + +With `npm`: + + npm run deploy + +or with `yarn`: + + yarn run deploy + +## Running the tests + +See [Contributing][contributing]. + +[appengine]: https://cloud.google.com/appengine/docs/flexible/nodejs +[readme]: ../README.md +[contributing]: https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/master/CONTRIBUTING.md + diff --git a/appengine/websockets/app.js b/appengine/websockets/app.js new file mode 100644 index 0000000000..8ffc3a5216 --- /dev/null +++ b/appengine/websockets/app.js @@ -0,0 +1,42 @@ +/** + * Copyright 2018, Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +// [START appengine_websockets_app] +const app = require('express')(); +app.set('view engine', 'pug'); + +const server = require('http').Server(app); +const io = require('socket.io')(server); + +app.get('/', (req, res) => { + res.render('index.pug'); +}); + +io.on('connection', (socket) => { + socket.on('chat message', (msg) => { + io.emit('chat message', msg); + }); +}); + +if (module === require.main) { + const PORT = process.env.PORT || 8080; + server.listen(PORT, () => { + console.log(`App listening on port ${PORT}`); + console.log('Press Ctrl+C to quit.'); + }); +} +// [END appengine_websockets_app] diff --git a/appengine/websockets/app.yaml b/appengine/websockets/app.yaml new file mode 100644 index 0000000000..3f6a3bf2ec --- /dev/null +++ b/appengine/websockets/app.yaml @@ -0,0 +1,27 @@ +# Copyright 2018, Google LLC. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# [START appengine_websockets_yaml] +runtime: nodejs +env: flex + +# Use only a single instance, so that this local-memory-only chat app will work +# consistently with multiple users. To work across multiple instances, an +# extra-instance messaging system or data store would be needed. +manual_scaling: + instances: 1 + +network: + session_affinity: true +# [END appengine_websockets_yaml] + diff --git a/appengine/websockets/package.json b/appengine/websockets/package.json new file mode 100644 index 0000000000..f347282771 --- /dev/null +++ b/appengine/websockets/package.json @@ -0,0 +1,37 @@ +{ + "name": "appengine-websockets", + "description": "Node.js websockets sample for Google App Engine", + "version": "0.0.1", + "private": true, + "license": "Apache Version 2.0", + "author": "Google Inc.", + "engines": { + "node": ">=4" + }, + "scripts": { + "deploy": "gcloud app deploy", + "start": "node app.js", + "lint": "samples lint", + "pretest": "npm run lint", + "test": "node app.js & ava -T 30s test/*.js; killall node", + "e2e-test": "samples test deploy" + }, + "dependencies": { + "express": "4.15.4", + "pug": "2.0.0-rc.3", + "socket.io": "2.0.3" + }, + "devDependencies": { + "@google-cloud/nodejs-repo-tools": "1.4.17", + "puppeteer": "^1.11.0" + }, + "cloud-repo-tools": { + "test": { + "app": { + "msg": "messages" + } + }, + "requiresKeyFile": true, + "requiresProjectId": true + } +} diff --git a/appengine/websockets/test/index.test.js b/appengine/websockets/test/index.test.js new file mode 100644 index 0000000000..a2490a19d3 --- /dev/null +++ b/appengine/websockets/test/index.test.js @@ -0,0 +1,46 @@ +/** + * Copyright 2018, Google LLC. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +'use strict'; + +const test = require('ava'); + +const puppeteer = require('puppeteer'); + +let browser, browserPage; + +test.before(async () => { + browser = await puppeteer.launch(); + browserPage = await browser.newPage(); +}); + +test.after.always(async () => { + await browser.close(); +}); + +test('should process chat message', async (t) => { + await browserPage.goto('http://localhost:8080'); + + await browserPage.evaluate(() => { + document.querySelector('input').value = 'test'; + document.querySelector('button').click(); + }); + + await new Promise(resolve => setTimeout(resolve, 100)); + + const itemText = await browserPage.evaluate(() => document.querySelector('li').textContent); + + t.is(itemText, 'test'); +}); diff --git a/appengine/websockets/views/index.pug b/appengine/websockets/views/index.pug new file mode 100644 index 0000000000..142ecb72b8 --- /dev/null +++ b/appengine/websockets/views/index.pug @@ -0,0 +1,58 @@ +//- Copyright 2018 Google LLC. +//- +//- Licensed under the Apache License, Version 2.0 (the "License"); +//- you may not use this file except in compliance with the License. +//- You may obtain a copy of the License at +//- +//- http://www.apache.org/licenses/LICENSE-2.0 +//- +//- Unless required by applicable law or agreed to in writing, software +//- distributed under the License is distributed on an "AS IS" BASIS, +//- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +//- See the License for the specific language governing permissions and +//- limitations under the License. + +//- [START appengine_websockets_index] +doctype html +html(lang="en") + head + title Socket.IO chat on App Engine + meta(charset="utf-8") + style. + * { margin: 0; padding: 0; box-sizing: border-box; } + body { font: 13px Helvetica, Arial; } + form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; } + form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; } + form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; } + #messages { list-style-type: none; margin: 0; padding: 0; } + #messages li { padding: 5px 10px; } + #messages li:nth-child(odd) { background: #eee; } + //- [START appengine_websockets_form] + body + ul(id="messages") + form(action="") + input(id="m" autocomplete="off") + button Send + //- [END appengine_websockets_form] + + script(src="/socket.io/socket.io.js") + script(src="https://code.jquery.com/jquery-1.11.1.js") + script. + // [START appengine_websockets_js] + $(function () { + var socket = io(); + $('form').submit(function(){ + console.log($('#m').val()); + socket.emit('chat message', $('#m').val()); + $('#m').val(''); + return false; + }); + socket.on('chat message', function(msg){ + console.log(msg); + $('#messages').append($('
  • ').text(msg)); + window.scrollTo(0, document.body.scrollHeight); + }); + }); + // [END appengine_websockets_js] +//- [END appengine_websockets_index] + From 80acbdd53a464d5c44b1dcb1a211c238ff2aad8b Mon Sep 17 00:00:00 2001 From: Ace Nassri Date: Tue, 4 Dec 2018 16:09:48 -0800 Subject: [PATCH 2/2] Fix lint + update copyright --- appengine/websockets/app.js | 4 ++-- appengine/websockets/app.yaml | 13 ------------- appengine/websockets/package.json | 2 +- appengine/websockets/test/index.test.js | 10 +++++++--- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/appengine/websockets/app.js b/appengine/websockets/app.js index 8ffc3a5216..b85c136a85 100644 --- a/appengine/websockets/app.js +++ b/appengine/websockets/app.js @@ -26,8 +26,8 @@ app.get('/', (req, res) => { res.render('index.pug'); }); -io.on('connection', (socket) => { - socket.on('chat message', (msg) => { +io.on('connection', socket => { + socket.on('chat message', msg => { io.emit('chat message', msg); }); }); diff --git a/appengine/websockets/app.yaml b/appengine/websockets/app.yaml index 3f6a3bf2ec..78189a14fe 100644 --- a/appengine/websockets/app.yaml +++ b/appengine/websockets/app.yaml @@ -1,16 +1,3 @@ -# Copyright 2018, Google LLC. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - # [START appengine_websockets_yaml] runtime: nodejs env: flex diff --git a/appengine/websockets/package.json b/appengine/websockets/package.json index f347282771..589340cf41 100644 --- a/appengine/websockets/package.json +++ b/appengine/websockets/package.json @@ -6,7 +6,7 @@ "license": "Apache Version 2.0", "author": "Google Inc.", "engines": { - "node": ">=4" + "node": ">=8" }, "scripts": { "deploy": "gcloud app deploy", diff --git a/appengine/websockets/test/index.test.js b/appengine/websockets/test/index.test.js index a2490a19d3..c333f91b31 100644 --- a/appengine/websockets/test/index.test.js +++ b/appengine/websockets/test/index.test.js @@ -15,9 +15,11 @@ 'use strict'; -const test = require('ava'); +/* eslint node/no-extraneous-require: "off" */ +const test = require('ava'); const puppeteer = require('puppeteer'); +/* global document */ let browser, browserPage; @@ -30,7 +32,7 @@ test.after.always(async () => { await browser.close(); }); -test('should process chat message', async (t) => { +test('should process chat message', async t => { await browserPage.goto('http://localhost:8080'); await browserPage.evaluate(() => { @@ -40,7 +42,9 @@ test('should process chat message', async (t) => { await new Promise(resolve => setTimeout(resolve, 100)); - const itemText = await browserPage.evaluate(() => document.querySelector('li').textContent); + const itemText = await browserPage.evaluate( + () => document.querySelector('li').textContent + ); t.is(itemText, 'test'); });