├── gimmevents-mouse.gif
├── gimmevents-keyboard.gif
├── src
├── main.js
├── _priv.js
├── emitter.js
├── receiver.js
└── base.js
├── srv
├── client-base.js
├── client-receiver.js
├── pair.js
├── client-emitter.js
└── main.js
├── .eslintrc
├── dist
├── emitter.html
└── receiver.html
├── utils
└── run.js
├── rollup.config.js
├── package.json
├── LICENSE
├── .gitignore
├── .fancom
└── README.md
/gimmevents-mouse.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbe/gimmevents/HEAD/gimmevents-mouse.gif
--------------------------------------------------------------------------------
/gimmevents-keyboard.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michalbe/gimmevents/HEAD/gimmevents-keyboard.gif
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import { Emitter } from './emitter';
2 | import { Receiver } from './receiver';
3 |
4 | export default {
5 | Emitter,
6 | Receiver
7 | };
8 |
--------------------------------------------------------------------------------
/srv/client-base.js:
--------------------------------------------------------------------------------
1 | class ClientBase {
2 | constructor({ connection, role, pair }) {
3 | this.connection = connection;
4 | this.role = role;
5 | this.pair = pair;
6 | }
7 | }
8 |
9 | module.exports = ClientBase;
10 |
--------------------------------------------------------------------------------
/srv/client-receiver.js:
--------------------------------------------------------------------------------
1 | const ClientBase = require('./client-base');
2 |
3 | class ClientReceiver extends ClientBase {
4 | constructor(options) {
5 | options.role = 'receiver';
6 | super(options);
7 | }
8 | }
9 |
10 | module.exports = ClientReceiver;
11 |
--------------------------------------------------------------------------------
/src/_priv.js:
--------------------------------------------------------------------------------
1 | const privates = [
2 | 'connect', 'on_connect', 'setup_listeners', 'send', 'listeners', 'on_message',
3 | 'handle_message'
4 | ];
5 |
6 | export const priv = privates.reduce((memo, item) => {
7 | memo[item] = Symbol(item);
8 | return memo;
9 | }, {});
10 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "extends": "defaults",
4 | "parser": "babel-eslint",
5 | "env": {
6 | "browser": true,
7 | "es6": true
8 | },
9 | "rules": {
10 | "comma-dangle": [1, "only-multiline"],
11 | "no-console": 0
12 | },
13 | "globals": {
14 | "FB": false
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/dist/emitter.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Emitter - Gimmevents
4 |
5 |
6 | I'm the Emitter. Press any key.
7 |
8 |
18 |
19 |
--------------------------------------------------------------------------------
/srv/pair.js:
--------------------------------------------------------------------------------
1 | const CLIENT_ROLES = ['emitter', 'receiver'];
2 |
3 | class Pair {
4 | constructor({ key }) {
5 | this.key = key;
6 | }
7 |
8 | assign_client(client) {
9 | if (!CLIENT_ROLES.includes(client.role)) {
10 | throw new Error(`${client.role} is not a proper role.`);
11 | return;
12 | }
13 |
14 | this[client.role] = client;
15 | client.pair = this;
16 | }
17 |
18 | get_client_by_role(role) {
19 | return this[role];
20 | }
21 |
22 | };
23 |
24 | module.exports = Pair;
25 |
--------------------------------------------------------------------------------
/dist/receiver.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Receiver - Gimmevents
4 |
5 |
6 | Other client pressed: nothing yet.
7 |
8 |
20 |
21 |
--------------------------------------------------------------------------------
/srv/client-emitter.js:
--------------------------------------------------------------------------------
1 | const ClientBase = require('./client-base');
2 |
3 | class ClientEmitter extends ClientBase {
4 | constructor(options) {
5 | options.role = 'emitter';
6 | super(options);
7 |
8 | this.connection.on('message', (msg) => {
9 | msg = JSON.parse(msg);
10 | if (msg.action === 'update') {
11 | const receiver = this.pair.get_client_by_role('receiver');
12 | if (!receiver) {
13 | return;
14 | }
15 |
16 | receiver.connection.send(JSON.stringify(msg));
17 | }
18 | });
19 | }
20 | }
21 |
22 | module.exports = ClientEmitter;
23 |
--------------------------------------------------------------------------------
/src/emitter.js:
--------------------------------------------------------------------------------
1 | import { priv } from './_priv';
2 | import { Base } from './base';
3 |
4 | export class Emitter extends Base {
5 | constructor(options = {}) {
6 | options.role = 'emitter';
7 | super(options);
8 | }
9 |
10 | register(event) {
11 | document.addEventListener(event, (evt) => {
12 | const {
13 | keyCode,
14 | which,
15 | key,
16 | screenX,
17 | screenY,
18 | clientX,
19 | clientY
20 | } = evt;
21 |
22 | this[priv.send]({
23 | action: 'update',
24 | event_name: event,
25 | event_data: {
26 | keyCode,
27 | which,
28 | key,
29 | screenX,
30 | screenY,
31 | clientX,
32 | clientY
33 | }
34 | })
35 | });
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/receiver.js:
--------------------------------------------------------------------------------
1 | import { priv } from './_priv';
2 | import { Base } from './base';
3 |
4 | export class Receiver extends Base {
5 | constructor(options = {}) {
6 | options.role = 'receiver';
7 | super(options);
8 |
9 | this[priv.listeners] = [];
10 | }
11 |
12 | on(event, handler) {
13 | this[priv.listeners].push({
14 | event_name: event,
15 | handler,
16 | });
17 | }
18 |
19 | [priv.handle_message] (message) {
20 | this[priv.listeners].forEach((listener) => {
21 | if (listener.event_name === message.event_name) {
22 | listener.handler(message.event_data);
23 | }
24 | })
25 | }
26 |
27 | [priv.setup_listeners] () {
28 | this[priv.on_message](this[priv.handle_message].bind(this));
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/utils/run.js:
--------------------------------------------------------------------------------
1 | const nodemon = require('nodemon');
2 | const spawn = require('child_process').spawn;
3 |
4 |
5 | nodemon({
6 | script: 'srv/main.js',
7 | ext: 'js json',
8 | ignore: ["dist/*", "src/*"],
9 | });
10 |
11 | nodemon.on('start', () => {
12 | console.log('Server has started');
13 | }).on('quit', () => {
14 | console.log('Server has quit');
15 | process.exit();
16 | }).on('restart', (files) => {
17 | console.log('Server restarted due to: ', files);
18 | });
19 |
20 |
21 | const cmd_process = spawn(
22 | 'node', [
23 | 'node_modules/rollup/bin/rollup',
24 | '-c', '-w'
25 | ]
26 | );
27 |
28 | cmd_process.stdout.on('data', data => {
29 | console.log(data.toString());
30 | });
31 |
32 | cmd_process.stderr.on('data', data => {
33 | console.log(data.toString());
34 | });
35 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import commonjs from 'rollup-plugin-commonjs';
2 | import resolve from 'rollup-plugin-node-resolve';
3 | import babel from 'rollup-plugin-babel';
4 | import serve from 'rollup-plugin-serve'
5 | import livereload from 'rollup-plugin-livereload'
6 |
7 | export default {
8 | input: 'src/main.js',
9 | output: {
10 | name: 'Gimmevents',
11 | format: 'umd',
12 | file: 'dist/gimmevents.js'
13 | },
14 | plugins: [
15 | commonjs({
16 | include: 'node_modules/**',
17 | }),
18 | resolve({
19 | customResolveOptions: {
20 | moduleDirectory: 'node_modules'
21 | }
22 | }),
23 | babel({
24 | exclude: 'node_modules/**'
25 | }),
26 | serve({
27 | contentBase: ['dist']
28 | }),
29 | livereload({
30 | watch: ['dist']
31 | })
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gimmevents",
3 | "version": "0.0.1",
4 | "author": "Michał Budzyński ",
5 | "repository": "git@github.com:michalbe/gimmevents.git",
6 | "devDependencies": {
7 | "babel-core": "^6.26.0",
8 | "babel-eslint": "^8.2.1",
9 | "babel-plugin-external-helpers": "^6.22.0",
10 | "babel-preset-latest": "^6.24.1",
11 | "eslint": "^4.17.0",
12 | "eslint-config-defaults": "^9.0.0",
13 | "nodemon": "^1.14.12",
14 | "rollup": "^0.55.5",
15 | "rollup-plugin-babel": "^3.0.3",
16 | "rollup-plugin-commonjs": "^8.3.0",
17 | "rollup-plugin-livereload": "^0.6.0",
18 | "rollup-plugin-node-resolve": "^3.0.2",
19 | "rollup-plugin-serve": "^0.4.2",
20 | "rollup-watch": "^4.3.1"
21 | },
22 | "scripts": {
23 | "build": "rollup -c",
24 | "dev": "node utils/run.js"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/srv/main.js:
--------------------------------------------------------------------------------
1 | const port = 40510;
2 |
3 | const WSServer = require('ws').Server;
4 | const Pair = require('./pair');
5 |
6 | const server = new WSServer({ port });
7 | const pairs = new Map();
8 |
9 | const Clients = {
10 | 'emitter': require('./client-emitter'),
11 | 'receiver': require('./client-receiver')
12 | };
13 |
14 | server.on('connection', (ws) => {
15 | ws.on('message', (message) => {
16 | message = JSON.parse(message);
17 | if (!pairs.has(message.key)) {
18 | const pair = new Pair({
19 | key: message.key
20 | });
21 | pairs.set(message.key, pair);
22 | }
23 |
24 | const pair = pairs.get(message.key);
25 | if (message.action === 'create') {
26 | if (pair.get_client_by_role(message.role)) {
27 | ws.send(JSON.stringify({
28 | message: `This pair already have a ${message.role}`
29 | }));
30 | ws.terminate();
31 | } else {
32 | const client = new Clients[message.role]({
33 | connection: ws
34 | });
35 |
36 | pair.assign_client(client);
37 | }
38 | }
39 | });
40 | });
41 |
--------------------------------------------------------------------------------
/src/base.js:
--------------------------------------------------------------------------------
1 | import { priv } from './_priv';
2 |
3 | export class Base {
4 | constructor({ key, host, role }) {
5 | this.key = key;
6 | this.host = host;
7 | this.role = role;
8 | this[priv.connect]();
9 | }
10 |
11 | [priv.connect] () {
12 | this.socket_handle = new WebSocket(`ws://${host}`);
13 | this.socket_handle.onopen = this[priv.on_connect].bind(this);
14 | }
15 |
16 | [priv.on_connect] () {
17 | console.log('Connected ...');
18 | this.is_connected = true;
19 | this[priv.send]({ action: 'create' });
20 | this[priv.setup_listeners]();
21 | }
22 |
23 | [priv.send] ({ action, event_name = '', event_data = {} }) {
24 | this.socket_handle.send(JSON.stringify({
25 | key: this.key,
26 | role: this.role,
27 | action,
28 | event_name,
29 | event_data
30 | }));
31 | }
32 |
33 | [priv.on_message] (handler) {
34 | if (typeof handler !== 'function') {
35 | return;
36 | }
37 |
38 | this.socket_handle.onmessage = (msg) => {
39 | const data = JSON.parse(msg.data);
40 | handler(data);
41 | };
42 | }
43 |
44 | [priv.setup_listeners] () {
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Michał Budzyński
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (http://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # Typescript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | .DS_Store
61 | dist/*.js
62 |
--------------------------------------------------------------------------------
/.fancom:
--------------------------------------------------------------------------------
1 | # ____
2 | # / __/___ _____ _________ ____ ___
3 | # / /_/ __ `/ __ \/ ___/ __ \/ __ `__ \
4 | # / __/ /_/ / / / / /__/ /_/ / / / / / /
5 | # /_/ \__,_/_/ /_/\___/\____/_/ /_/ /_/
6 | #
7 | # Fancy Commit Manager Config file
8 | # https://github.com/michalbe/fancom
9 |
10 | # Name of the repository, default: name of the current repo
11 | #reponame="reponame"
12 |
13 | # Do we want to push to the temporary branch?
14 | pushtotemp=0
15 |
16 | # After pushing to the repo, what branch needs to be compared?
17 | #comparewith="branch to compare"
18 |
19 | # Suffix added to the temporary created, developer's branch, default: 'temp'
20 | #devsuffix="temp"
21 |
22 | # Set to `1` to add random emoji file to the commit message, default: no
23 | emoji=1
24 |
25 | # Add all the changes made in the repo to the commit, default: no
26 | #addall=1
27 |
28 | # Prefix added to the commit message, default: [REPONAME]
29 | #prefix="Shit's on fire yo"
30 |
31 | # Add branch name to the prefix of the commit, default: no
32 | #branchincommit=1
33 |
34 | # Skip the prefix in the commit message, default: no
35 | noprefix=1
36 |
37 | # Open github compare page in the browser after succesfull push
38 | opencompare=0
39 |
40 | # Github username, default: username of the owner of the current repo
41 | #author="authorname"
42 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | gimmevents
2 | ---
3 | by [@michalbe](http://github.com/michalbe)
4 |
5 | ## WHAT?
6 | [Gimmevents](https://github.com/michalbe/gimmevents) is a simple event tunnel that sends keyboard and mouse events from one browser to another. CLients needs to register with a unique key.
7 |
8 | ## WHY?
9 | I don't have any VR controller what makes my [a-frame](https://github.com/aframevr/aframe) based games boring and non-interactive. With [Gimmevents](https://github.com/michalbe/gimmevents) I can render game on my VR set (for now it's just my phone) and still control it with my computer's keyboard or mouse.
10 |
11 | ## HOW?
12 | [Gimmevents](https://github.com/michalbe/gimmevents) uses [WebSockets](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API) and custom pairing system to send events from one browser to another.
13 |
14 | ### Keyboard events
15 | 
16 |
17 | ### Mouse events
18 | 
19 |
20 | ## API
21 | ### Emitter
22 | ```javascript
23 | const gimmevents = new Gimmevents.Emitter({
24 | key: my_unique_session_key,
25 | host: host
26 | });
27 |
28 | gimmevents.register(event);
29 | ```
30 |
31 | ### Receiver
32 | ```javascript
33 | const gimmevents = new Gimmevents.Receiver({
34 | key: my_unique_session_key,
35 | host: host
36 | });
37 |
38 | gimmevents.on(event, (e) => {
39 | // Do stuff
40 | });
41 | ```
42 |
43 | ## EVENTS
44 | - keyup
45 | - keydown
46 | - keypress
47 | - mousemove
48 | - mouseup
49 | - mousedown
50 |
51 | ## Development
52 |
53 | ```bash
54 | git clone git@github.com:michalbe/gimmevents.git
55 | cd gimmevents
56 | npm i
57 | npm run dev
58 | ```
59 |
60 | then open [localhost:7100/emitter.html](http://localhost:7100/emitter.html) in one browser and [localhost:7100/receiver.html](http://localhost:7100/receiver.html) in another. Demo listened to all the keys pressed in one window and sends them to another.
61 |
--------------------------------------------------------------------------------