Compare commits
129 commits
Author | SHA1 | Date | |
---|---|---|---|
d3c07e19b8 | |||
6b6a25f28a | |||
d2b779540b | |||
3dda921db2 | |||
|
a9c0e7592e | ||
|
8ceb715798 | ||
|
558b2dff35 | ||
|
c91a1c105b | ||
|
2197a1ef46 | ||
|
d6b5baeb7c | ||
|
2b7e5bcffe | ||
|
d4e6c5cdb1 | ||
|
82a498a4ff | ||
|
6e8816c467 | ||
|
28a8cf8b31 | ||
|
cd4e146aa9 | ||
|
2f74ef5ca7 | ||
|
9087608087 | ||
|
ec66f19932 | ||
|
57345250bd | ||
|
5af420d411 | ||
|
26b3661017 | ||
|
47687efa9f | ||
|
e4920b542c | ||
|
6b88e32cb0 | ||
|
a555afd065 | ||
|
a07ab11f8f | ||
|
d8706f18e5 | ||
|
8ab38d8a41 | ||
|
699e2e0ed2 | ||
|
74bdf5dfef | ||
|
5b248f46dd | ||
|
ed04c1510c | ||
|
18e601de20 | ||
|
b2a2055b99 | ||
|
175022f041 | ||
|
baa9601db5 | ||
|
f7a44991a4 | ||
|
aad78afe82 | ||
|
975dc18377 | ||
|
d5ac8ff58c | ||
|
189aac54a5 | ||
|
fb190db4c8 | ||
|
1d355b0271 | ||
|
6ae2183385 | ||
|
78acd03746 | ||
|
9b484d4158 | ||
|
5089974c22 | ||
|
94ae46ee9a | ||
|
4fff368db7 | ||
|
86abee6965 | ||
|
6775b427a5 | ||
|
5015caf55a | ||
|
fd8e8a9915 | ||
|
1cf7a8f4c6 | ||
|
9e985b05f0 | ||
|
9583845466 | ||
|
721e80aa1f | ||
|
9e16242c2c | ||
|
005ad02f68 | ||
|
4f0016e150 | ||
|
ddd1005141 | ||
|
799b6c5b84 | ||
|
f5c6a3b2b5 | ||
|
7dc1d385ab | ||
|
dc1e7091f3 | ||
|
2eb2c8ad31 | ||
|
ab532cb938 | ||
|
133f9e094c | ||
|
e09b3ee92f | ||
|
1bf3400ba3 | ||
|
a6adc87e08 | ||
|
d55ae52565 | ||
|
60b24afdbf | ||
|
2f86cffc6e | ||
|
4cb0798467 | ||
|
14ea29b81a | ||
|
14c2e2ff42 | ||
|
5082ba7680 | ||
|
67538fdb7f | ||
|
c417f6b5d3 | ||
|
edbc9ce43d | ||
|
a13dd84d21 | ||
|
fdd57e3f11 | ||
|
0c28ef6a65 | ||
|
10384f956d | ||
|
dd8529bd88 | ||
|
889e46b291 | ||
|
8068c81e2e | ||
|
c0408a614f | ||
|
96839665e3 | ||
|
99e9978fb2 | ||
|
bbee06f7d5 | ||
|
ed43e64a75 | ||
|
1fba5fb0e3 | ||
|
9d875524ec | ||
|
af79206742 | ||
|
3461547dec | ||
|
e221e9b662 | ||
|
cb37d41502 | ||
|
276220eaf4 | ||
|
65d8a1c122 | ||
|
a72cd394ad | ||
|
b0d45d4b69 | ||
|
0ff7bc0ed7 | ||
|
f491373abe | ||
|
1ad2108e71 | ||
|
4f4f7c4325 | ||
|
507df4ea58 | ||
|
fb38738d2f | ||
|
1216332270 | ||
|
99a37e0547 | ||
|
30937e566b | ||
|
69775a0f42 | ||
|
4060be4bc0 | ||
|
2f103b849b | ||
|
c855c39b33 | ||
|
f4277cb0f5 | ||
|
1ec673b249 | ||
|
37775bd4e2 | ||
|
4d927ea74f | ||
|
f90679703c | ||
|
b11f942272 | ||
|
785c8a08f1 | ||
|
0b7b87841b | ||
|
5d3e723b3d | ||
|
e8a961f4a3 | ||
|
0eaa5c92a7 | ||
|
fc8831d017 |
16
.dockerignore
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Node.js/npm stuff
|
||||
node_modules
|
||||
npm-debug.log
|
||||
|
||||
# Cryptalk documentation
|
||||
docs
|
||||
|
||||
# Git stuff
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Travis CI configuration
|
||||
.travis.yml
|
||||
|
||||
# Heroku metadata
|
||||
app.json
|
2
.eslintignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
client/public/js/cryptalk.min.js
|
||||
node_modules
|
38
.eslintrc.json
Normal file
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"commonjs": true,
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"amd": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
"tab"
|
||||
],
|
||||
"linebreak-style": 0,
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-undef": [
|
||||
"warn"
|
||||
],
|
||||
"no-console": [
|
||||
"warn"
|
||||
]
|
||||
}
|
||||
}
|
2
.github/FUNDING.yml
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
github: [hexagon]
|
||||
ko_fi: hexagon_56k
|
6
.github/dependabot.yml
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
71
.github/workflows/codeql-analysis.yml
vendored
Normal file
|
@ -0,0 +1,71 @@
|
|||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ master ]
|
||||
schedule:
|
||||
- cron: '45 11 * * 3'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
30
.github/workflows/node.js.yml
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
- run: npm ci
|
||||
- run: npm run build:ci
|
61
.gitignore
vendored
|
@ -1,29 +1,32 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# Commenting this out is preferred by some people, see
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
|
||||
node_modules
|
||||
|
||||
# Users Environment Variables
|
||||
.lock-wscript
|
||||
docs/Thumbs.db
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# Commenting this out is preferred by some people, see
|
||||
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
|
||||
node_modules
|
||||
|
||||
# Users Environment Variables
|
||||
.lock-wscript
|
||||
docs/Thumbs.db
|
||||
|
||||
# Vim temp files
|
||||
*.swp
|
||||
|
|
18
.npmignore
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Docker stuff
|
||||
.dockerignore
|
||||
Dockerfile
|
||||
docker-entrypoint.sh
|
||||
|
||||
# Heroku stuff
|
||||
app.json
|
||||
|
||||
# Source code
|
||||
client/source/
|
||||
|
||||
# Dev config
|
||||
rollup.config.js
|
||||
.eslintignore
|
||||
.eslintrc.json
|
||||
|
||||
# Github stuff
|
||||
.github
|
6
Dockerfile
Normal file
|
@ -0,0 +1,6 @@
|
|||
FROM keymetrics/pm2:16-alpine
|
||||
COPY . /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
RUN npm install --no-cache --production
|
||||
EXPOSE 8080
|
||||
CMD [ "pm2-runtime", "start", "pm2.json" ]
|
|
@ -1,22 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Robin Nilsson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2021 Hexagon <github.com/Hexagon>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
157
README.md
|
@ -1,39 +1,120 @@
|
|||

|
||||

|
||||
|
||||
Cyptalk is a HTML5/Node.js based encrypted instant chat
|
||||

|
||||
[](https://badge.fury.io/js/cryptalk)
|
||||
[](LICENSE.md)
|
||||
[](https://www.codacy.com/gh/Hexagon/cryptalk/dashboard?utm_source=github.com&utm_medium=referral&utm_content=Hexagon/cryptalk&utm_campaign=Badge_Grade)
|
||||
|
||||
Features
|
||||
========
|
||||
# Cryptalk
|
||||
|
||||
* Client side AES-256-CBC encryption/decryption (the server is just a messenger)
|
||||
* 256 bit key derived from your passphrase using PBKDF2
|
||||
* Optional nicknames
|
||||
* Random (UUID v4) channel name generation för less guessability
|
||||
* Quick-links (not recommended) using http://server/#Room:Passphrase
|
||||
* Super simple setup
|
||||
* Notification sounds (mutable)
|
||||
* Native popup notifications
|
||||
* Configurable page title
|
||||
Cyptalk is a HTML5/Node.js based, client side (E2EE) encrypted instant chat
|
||||
|
||||
## Features
|
||||
|
||||
Regular setup
|
||||
========
|
||||
* Client side AES-256-CBC encryption/decryption (the server is just a messenger)
|
||||
* 256 bit key derived from your passphrase using PBKDF2
|
||||
* Messages torched after a configurable delay, default is 600s.
|
||||
* Simple setup using npm, Docker or Heroku
|
||||
* Notification sounds (mutable)
|
||||
* Native popup notifications
|
||||
* Configurable page title
|
||||
* Nicknames, optional.
|
||||
* Quick-links using http://server/#Room:Passphrase, optional and insecure
|
||||
|
||||
## Installing
|
||||
|
||||
### Docker setup
|
||||
|
||||
To run latest cryptalk with docker, exposed on host port 80, simply run the following command to pull it from docker hub
|
||||
|
||||
```bash
|
||||
sudo docker run -d --restart=always -p 80:8080 hexagon/cryptalk
|
||||
```
|
||||
|
||||
### Heroku setup
|
||||
|
||||
Click the button below
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/hexagon/cryptalk)
|
||||
|
||||
### Docker setup without using docker hub
|
||||
|
||||
Clone this repo, enter the new directory.
|
||||
|
||||
Build image
|
||||
```bash
|
||||
docker build . --tag="hexagon/cryptalk"
|
||||
```
|
||||
|
||||
Run container, enable start on boot, expose to port 80 at host
|
||||
```bash
|
||||
sudo docker run -d --restart=always -p 80:8080 hexagon/cryptalk
|
||||
```
|
||||
|
||||
Browse to ```http://<ip-of-server>/```
|
||||
|
||||
Done!
|
||||
|
||||
### npm setup
|
||||
|
||||
Install node.js, exact procedure is dependant on platform and distribution.
|
||||
|
||||
Create a now folder, browse to it, and install the app from npm
|
||||
Install the app from npm
|
||||
```bash
|
||||
npm install cryptalk
|
||||
npm install cryptalk -g
|
||||
````
|
||||
|
||||
Then issue the following to start the app
|
||||
|
||||
```bash
|
||||
cryptalk
|
||||
```
|
||||
|
||||
Browse to ```http://localhost:8080```
|
||||
|
||||
Done!
|
||||
|
||||
Developer setup
|
||||
========
|
||||
## Usage
|
||||
|
||||
Install node.js, exact procedure is dependant on platform and distribution.
|
||||
```
|
||||
|
||||
Available commands:
|
||||
|
||||
Client:
|
||||
/key StrongPassphrase Sets encryption key
|
||||
/nick NickName Sets an optional nick
|
||||
/mute Audio on
|
||||
/unmute Audio off
|
||||
/clear Clear on-screen buffer
|
||||
/help This
|
||||
/title Set your local page title
|
||||
/torch AfterSeconds Console messages are torched
|
||||
after this amount of seconds
|
||||
(default 600).
|
||||
|
||||
Room:
|
||||
/join RoomId Join a room
|
||||
/leave Leave the room
|
||||
/count Count participants
|
||||
|
||||
Host:
|
||||
/connect Connect to host
|
||||
/disconnect Disconnect from host
|
||||
|
||||
You can select any of the five last commands/messages with up/down key.
|
||||
|
||||
Due to security reasons, /key command is not saved, and command
|
||||
history is automatically cleared after one minute of inactivity.
|
||||
|
||||
It is highly recommended to use incognito mode while chatting,
|
||||
to prevent browsers from keeping history or cache.
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Install node.js (development require >=12.0), exact procedure is dependant on platform and distribution.
|
||||
|
||||
Clone this repo
|
||||
```bash
|
||||
|
@ -48,41 +129,9 @@ npm install
|
|||
|
||||
Start server
|
||||
```bash
|
||||
node server.js
|
||||
npm run start
|
||||
```
|
||||
|
||||
Browse to ```http://localhost:8080```
|
||||
|
||||
Usage
|
||||
========
|
||||
|
||||
```
|
||||
|
||||
Available commands:
|
||||
|
||||
Client:
|
||||
/key StrongPassphrase Sets encryption key
|
||||
/nick NickName Sets an optional nick
|
||||
/mute Audio on
|
||||
/unmute Audio off
|
||||
/clear Clear on-screen buffer
|
||||
/help This
|
||||
/title Set your local page title
|
||||
|
||||
Room:
|
||||
/generate Generate random room
|
||||
/join RoomId Join a room
|
||||
/leave Leave the room
|
||||
/count Count participants
|
||||
|
||||
Host:
|
||||
/hosts List available hosts
|
||||
/connect HostIndex Connect to selected host
|
||||
/disconnect Disconnect from host
|
||||
|
||||
You can select any of the five last commands/messages with up/down key.
|
||||
|
||||
Due to security reasons, /key command is not saved, and command
|
||||
history is automatically cleared after one minute of inactivity.
|
||||
|
||||
```
|
||||
To work on the JavaScript, edit the code in ```client/source/```. To test the changes, first run ```npm run build``` to lint, build and minify the code. Then restart the server.
|
||||
|
|
12
SECURITY.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 4.0.x | :white_check_mark: |
|
||||
| < 4.0 | :x: |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Email hexagon@56k.guru. Do NOT report an issue, we will have a look at it asap.
|
16
app.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "Cryptalk",
|
||||
"description": "Client side (E2EE) encrypted instant chat",
|
||||
"keywords": [
|
||||
"cryptalk",
|
||||
"crypto-js",
|
||||
"AES",
|
||||
"secure",
|
||||
"html5",
|
||||
"encryption",
|
||||
"privacy",
|
||||
"chat",
|
||||
"e2ee"
|
||||
],
|
||||
"repository": "https://github.com/Hexagon/cryptalk"
|
||||
}
|
|
@ -1,35 +1,33 @@
|
|||
/*------------------------------------*\
|
||||
GENERIC
|
||||
\*------------------------------------*/
|
||||
html {
|
||||
-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
|
||||
-moz-box-sizing: border-box; /* Firefox, other Gecko */
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
box-sizing: border-box; /* Opera/IE 8+ */
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
|
||||
font-family: monospace, 'Courier New';
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
body, html {
|
||||
body,
|
||||
html {
|
||||
min-height: 100%;
|
||||
min-width: 600px;
|
||||
background-color: #000000;
|
||||
background-color: #181a1d;
|
||||
overflow: hidden;
|
||||
padding: 0px;
|
||||
margin:0px;
|
||||
color: #00DD00;
|
||||
margin: 0px;
|
||||
color: #00dd00;
|
||||
}
|
||||
|
||||
.good { color: #99FF99; }
|
||||
.good { color: #99ff99; }
|
||||
.bad { color: #ff7777; }
|
||||
.info { color:#99FFFF; }
|
||||
.info { color: #99ffff; }
|
||||
.neutral { color: #eeeeee; }
|
||||
|
||||
|
||||
/*------------------------------------*\
|
||||
CHAT
|
||||
\*------------------------------------*/
|
||||
|
@ -39,15 +37,19 @@ body, html {
|
|||
bottom: 40px;
|
||||
position: absolute;
|
||||
list-style-type: none;
|
||||
padding:0;
|
||||
margin:0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Messages */
|
||||
#chat li {
|
||||
white-space: pre;
|
||||
padding: 2px 15px;
|
||||
color: #343434;
|
||||
color: #808008;
|
||||
}
|
||||
|
||||
#chat li .timestamp {
|
||||
color: #a0a00a;
|
||||
}
|
||||
|
||||
/* Message types */
|
||||
|
@ -55,24 +57,27 @@ body, html {
|
|||
#chat i {
|
||||
font-style: normal;
|
||||
}
|
||||
#chat i.motd { color: #99FF99; display:inline-block; line-height: 12px !important; }
|
||||
#chat i.motd {
|
||||
color: #99ff99;
|
||||
display:inline-block;
|
||||
line-height: 12px !important;
|
||||
}
|
||||
#chat i.info { color: #999999; }
|
||||
#chat i.server { color: #99FFFF; }
|
||||
#chat i.server { color: #99ffff; }
|
||||
#chat i.error { color: #ff7777; }
|
||||
#chat i.message { color: #eeeeee; }
|
||||
#chat i.nick { color: #99FF99; }
|
||||
#chat i.nick { color: #99ff99; }
|
||||
#chat i.fatal { color: #ff7777; }
|
||||
|
||||
/*------------------------------------*\
|
||||
INPUT & LOADER
|
||||
\*------------------------------------*/
|
||||
#input_wrapper {
|
||||
right:0;
|
||||
bottom:0;
|
||||
left:0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
|
||||
height:30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#input,
|
||||
|
@ -87,24 +92,35 @@ body, html {
|
|||
|
||||
padding: 5px 5px 5px 15px;
|
||||
|
||||
color: #FFFFFF;
|
||||
background-color:#141414;
|
||||
color: #ffffff;
|
||||
background-color:#272a2e;
|
||||
height:30px;
|
||||
|
||||
border-top: 2px solid #153315;
|
||||
}
|
||||
|
||||
#input { z-index: 1; }
|
||||
#loader { z-index: 0; line-height: 20px; font-size: 14px; font-weight: 100; font-family: tahoma;}
|
||||
#input {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#loader {
|
||||
z-index: 0;
|
||||
line-height: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 100;
|
||||
font-family: tahoma;
|
||||
}
|
||||
|
||||
.loading #loader {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
/*------------------------------------*\
|
||||
SPINNER
|
||||
\*------------------------------------*/
|
||||
.loading #loader { z-index: 2; }
|
||||
.loading #loader span {
|
||||
margin-left:-2px;
|
||||
-webkit-animation: rotation 1s infinite linear;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotation {
|
||||
from {-webkit-transform: rotate(0deg);}
|
||||
to {-webkit-transform: rotate(359deg);}
|
||||
from { -webkit-transform: rotate(0deg); }
|
||||
to { -webkit-transform: rotate(359deg); }
|
||||
}
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.7 KiB |
Before Width: | Height: | Size: 8.2 KiB After Width: | Height: | Size: 8.2 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
Before Width: | Height: | Size: 628 B After Width: | Height: | Size: 628 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
@ -1,15 +1,13 @@
|
|||
<!doctype html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>Cryptalk</title>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="css/default.css">
|
||||
<meta charset="utf-8">
|
||||
|
||||
<head profile="http://www.w3.org/2005/10/profile">
|
||||
<link rel="icon"
|
||||
type="image/png"
|
||||
href="gfx/icon_32x32.png">
|
||||
<link rel="stylesheet" type="text/css" href="css/default.css">
|
||||
<link rel="icon" type="image/png" href="gfx/icon_32x32.png">
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
@ -25,9 +23,7 @@
|
|||
<input type="text" id="input" />
|
||||
</div>
|
||||
|
||||
<!-- Only include the script needed for loading the app -->
|
||||
<script src="js/vendor/fandango.v20140924.min.js"></script>
|
||||
<script src="js/bootstrap.js"></script>
|
||||
|
||||
<script src="js/cryptalk.min.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
1
client/public/js/cryptalk.min.js
vendored
Normal file
1
client/public/js/cryptalk.min.js.map
Normal file
36
client/source/$.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
import utils from './$.utils.js';
|
||||
import proto from './$.proto.js';
|
||||
|
||||
// Create a custom edition of Array, extended with $.proto
|
||||
function ElementArray () {}
|
||||
ElementArray.prototype = new Array;
|
||||
for(var k in proto) ElementArray.prototype[k] = proto[k];
|
||||
|
||||
// Create to actual dollar function
|
||||
function Dollar (selector) {
|
||||
|
||||
let matches = new ElementArray();
|
||||
|
||||
if (selector !== undefined) {
|
||||
if (selector === document) {
|
||||
matches.push(document);
|
||||
} else if (selector === window) {
|
||||
matches.push(window);
|
||||
} else {
|
||||
let match = document.querySelectorAll(selector);
|
||||
if (match) {
|
||||
for( var i=0; i < match.length; i++) {
|
||||
matches.push(match[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
|
||||
}
|
||||
|
||||
// Add utils to Dollar
|
||||
for(var l in utils) Dollar[l] = utils[l];
|
||||
|
||||
export default Dollar;
|
43
client/source/$.proto.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
var
|
||||
exports = {};
|
||||
|
||||
// Extremely naive implementations of .html() and .append()
|
||||
exports.html = function (string) {
|
||||
this.forEach(function (element) {
|
||||
element.innerHTML = string;
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
exports.append = function (string) {
|
||||
this.forEach(function (element) {
|
||||
element.innerHTML += string;
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
exports.first = function () {
|
||||
return this[0];
|
||||
};
|
||||
|
||||
// Naive implementations of .on()
|
||||
exports.on = function (eventName, callback) {
|
||||
this.forEach(function (element) {
|
||||
if (element.addEventListener) {
|
||||
element.addEventListener(eventName, callback, false);
|
||||
} else if (element.attachEvent) {
|
||||
element.attachEvent('on' + eventName, callback);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
exports.focus = function () {
|
||||
// It doesn't make sense to focus all matched elements. So we settle for the first one
|
||||
if(this[0]) {
|
||||
this[0].focus();
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
export default exports;
|
107
client/source/$.utils.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
/* global io */
|
||||
|
||||
import { AES, SHA1, enc } from 'crypto-js';
|
||||
|
||||
var
|
||||
exports = {},
|
||||
reDigits = /^\d+$/;
|
||||
|
||||
// Namespace websocket
|
||||
exports.io = io;
|
||||
|
||||
// Namespace SHA1
|
||||
exports.SHA1 = function (string) {
|
||||
return SHA1(string).toString();
|
||||
};
|
||||
|
||||
// Namespace encode
|
||||
exports.AES = {
|
||||
decrypt: function (string, fgh) {
|
||||
return AES.decrypt(string, fgh).toString(enc.Utf8);
|
||||
},
|
||||
|
||||
encrypt: function (string, fgh) {
|
||||
return AES.encrypt(string, fgh).toString();
|
||||
}
|
||||
};
|
||||
|
||||
exports.ssplit = function (string, seperator) {
|
||||
var components = string.split(seperator);
|
||||
return [components.shift(), components.join(seperator)];
|
||||
};
|
||||
|
||||
exports.activeElement = function () {
|
||||
try { return document.activeElement; } catch (e) { return; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all characters but 0 - 9 from given string.
|
||||
*
|
||||
* @method digits
|
||||
* @param {String} str The string to sanitize
|
||||
* @return {String} The sanitized string
|
||||
* @example
|
||||
* $.digits('foo8bar'); // `8`
|
||||
* $.digits('->#5*duckM4N!!!111'); // `54111`
|
||||
*/
|
||||
exports.isDigits = function(value) {
|
||||
return reDigits.test(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* A very simple templating function.
|
||||
* @param {} str [description]
|
||||
* @param {[type]} map [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
exports.template = function (str, map) {
|
||||
return str && str.replace(/{(\w+)}/gi, function(outer, inner) {
|
||||
return Object.prototype.hasOwnProperty.call(map, inner) ? map[inner] : outer /* '' */;
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
exports.getJSON = function (path, onSuccess, onError) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', path, true);
|
||||
|
||||
request.onreadystatechange = function() {
|
||||
if (this.readyState === 4) {
|
||||
if (this.status >= 200 && this.status < 400) {
|
||||
try {
|
||||
onSuccess && onSuccess(JSON.parse(this.responseText));
|
||||
} catch (e) {
|
||||
onError && onError();
|
||||
}
|
||||
} else {
|
||||
onError && onError();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
request.send();
|
||||
request = null;
|
||||
};
|
||||
|
||||
// Part of this is originating from mustasche.js
|
||||
// Code: https://github.com/janl/mustache.js/blob/master/mustache.js#L43
|
||||
// License: https://github.com/janl/mustache.js/blob/master/LICENSE
|
||||
exports.escapeHtml = (function () {
|
||||
var pattern = /[&<>"'/]/g,
|
||||
entities = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
'\'': ''',
|
||||
'/': '/'
|
||||
};
|
||||
|
||||
return function (string) {
|
||||
return String(string).replace(pattern, function (s) {
|
||||
return entities[s];
|
||||
});
|
||||
};
|
||||
}());
|
||||
|
||||
export default exports;
|
89
client/source/audio.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
/* Usage:
|
||||
|
||||
mediator.on('audio:play', playTones );
|
||||
mediator.on('audio:on', on );
|
||||
mediator.on('audio:off', off );
|
||||
mediator.on('audio:mute', mute );
|
||||
mediator.on('audio:unmute', unmute );
|
||||
|
||||
*/
|
||||
|
||||
// Sounds module, used for emitting those annoying bl-up sounds when receiving a message
|
||||
|
||||
import queue from './queue.js';
|
||||
import mediator from 'qbus';
|
||||
import templates from './templates.js';
|
||||
|
||||
var
|
||||
// Private variables
|
||||
ac = false,
|
||||
enabled = true,
|
||||
muted = false,
|
||||
|
||||
// Recursive function for playing tones
|
||||
// accepts an array of [tone,start_ms,duration_ms] - entries
|
||||
playTones = function (tones, i) {
|
||||
|
||||
// Parameter defaults
|
||||
i = (i === undefined) ? 0 : i;
|
||||
|
||||
// Stop if we've reached the end of iteration, and require ac, also stop if we're muted
|
||||
if (!ac || !enabled || !(i < Object.keys(tones).length) || muted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add tones to execution queue
|
||||
var current_tones = tones[i],
|
||||
freqs = current_tones[0],
|
||||
start = current_tones[1],
|
||||
duration = current_tones[2];
|
||||
|
||||
var o = ac.createOscillator();
|
||||
var g = ac.createGain();
|
||||
o.frequency.value = freqs;
|
||||
o.connect(g);
|
||||
g.gain.value = 0.25;
|
||||
g.connect(ac.destination);
|
||||
queue.add_function_delayed(start,function() { o.noteOn ? o.noteOn(0) : o.start(0); });
|
||||
queue.add_function_delayed(start+duration,function() { o.noteOff ? o.noteOff(0) : o.stop(0); });
|
||||
|
||||
// Iterate, or start playing
|
||||
i++;
|
||||
if( i < Object.keys(tones).length ) {
|
||||
playTones(tones,i);
|
||||
} else {
|
||||
queue.run();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
on = function() {
|
||||
enabled = true;
|
||||
},
|
||||
|
||||
off = function() {
|
||||
enabled = false;
|
||||
},
|
||||
|
||||
mute = function() {
|
||||
muted = true;
|
||||
mediator.emit('console:info',templates.messages.muted);
|
||||
},
|
||||
|
||||
unmute = function() {
|
||||
muted = false;
|
||||
mediator.emit('console:info',templates.messages.unmuted);
|
||||
};
|
||||
|
||||
// Find audio context
|
||||
if (window.AudioContext || window.webkitAudioContext) {
|
||||
ac = new (window.AudioContext || window.webkitAudioContext);
|
||||
}
|
||||
|
||||
// Connect events
|
||||
mediator.on('audio:play', function(tones) {playTones(tones); } );
|
||||
mediator.on('audio:on', on );
|
||||
mediator.on('audio:off', off );
|
||||
mediator.on('audio:mute', mute );
|
||||
mediator.on('audio:unmute', unmute );
|
87
client/source/client.js
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
|
||||
Accepts:
|
||||
mediator.on('command:help', ...);
|
||||
mediator.on('command:nick', ...);
|
||||
mediator.on('command:key', ...);
|
||||
mediator.on('command:key', ...);
|
||||
mediator.on('command:torch', ...);
|
||||
mediator.on('command:title', ...);
|
||||
|
||||
Emits:
|
||||
mediator.emit('nick:changed',...);
|
||||
mediator.emit('key:changed',...);
|
||||
mediator.emit('console:clear',...);
|
||||
mediator.emit('console:info',...);
|
||||
mediator.emit('console:error',...);
|
||||
|
||||
|
||||
*/
|
||||
import $ from './$.js';
|
||||
|
||||
export default function (mediator, settings, templates) {
|
||||
var
|
||||
// Private properties
|
||||
nick,
|
||||
key,
|
||||
|
||||
setKey = function(payload) {
|
||||
/*if (!host) {
|
||||
return post('error', templates.messages.key_no_host);
|
||||
}*/
|
||||
|
||||
// Make sure the key meets the length requirements
|
||||
if (payload.length > settings.key.maxLen) {
|
||||
return mediator.emit('console:error',templates.messages.key_to_long);
|
||||
} else if (payload.length < settings.key.minLen) {
|
||||
return mediator.emit('console:error',templates.messages.key_to_short);
|
||||
}
|
||||
|
||||
// Set key
|
||||
key = payload;
|
||||
|
||||
// Keep other modules informed
|
||||
mediator.emit('key:changed',key);
|
||||
|
||||
// Inform that the key has been set
|
||||
return mediator.emit('console:info', templates.messages.key_ok );
|
||||
},
|
||||
|
||||
help = function () { mediator.emit('console:motd', templates.help); },
|
||||
|
||||
clear = function () { mediator.emit('console:clear'); },
|
||||
|
||||
setTorch = function (payload) { mediator.emit('console:torch',payload); },
|
||||
|
||||
setNick = function (payload) {
|
||||
|
||||
// Make sure the nick meets the length requirements
|
||||
if (payload.length > settings.nick.maxLen) {
|
||||
return mediator.emit('console:error', $.template(templates.messages.nick_to_long, { nick_maxLen: settings.nick.maxLen } ));
|
||||
} else if (payload.length < settings.nick.minLen) {
|
||||
return mediator.emit('console:error', $.template(templates.messages.nick_to_short, {nick_minLen: settings.nick.minLen } ));
|
||||
}
|
||||
|
||||
// Set nick
|
||||
nick = payload;
|
||||
|
||||
// Keep other modules informed
|
||||
mediator.emit('nick:changed', nick);
|
||||
|
||||
// Inform that the nick has been set
|
||||
mediator.emit('console:info', $.template(templates.messages.nick_set, { nick: $.escapeHtml(nick)}));
|
||||
|
||||
},
|
||||
|
||||
title = function(payload) {
|
||||
mediator.emit('window:title',payload);
|
||||
mediator.emit('console:info', $.template(templates.messages.title_set, { title: $.escapeHtml(payload)}));
|
||||
};
|
||||
|
||||
mediator.on('command:help', help);
|
||||
mediator.on('command:clear', clear);
|
||||
mediator.on('command:nick', setNick);
|
||||
mediator.on('command:key', setKey);
|
||||
mediator.on('command:torch', setTorch);
|
||||
mediator.on('command:title', title);
|
||||
}
|
216
client/source/console.js
Normal file
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
|
||||
Accepts:
|
||||
mediator.on('console:clear', clear);
|
||||
mediator.on('console:torch', ttl)
|
||||
mediator.on('console:motd', motd);
|
||||
mediator.on('console:info', info);
|
||||
mediator.on('console:error', error);
|
||||
mediator.on('console:server', server);
|
||||
mediator.on('console:message', message);
|
||||
mediator.on('console:lockinput', lockInput);
|
||||
mediator.on('console:unlockInput', unlockInput);
|
||||
mediator.on('console:param', param);
|
||||
|
||||
Emits:
|
||||
mediator.emit('notification:send',...);
|
||||
mediator.emit('audio:play',...);
|
||||
ToDo
|
||||
*/
|
||||
import $ from './$.js';
|
||||
|
||||
export default function(mediator,settings,templates, sounds) {
|
||||
|
||||
var // Collection of DOM components
|
||||
components = {
|
||||
chat: $('#chat'),
|
||||
input: $('#input'),
|
||||
inputWrapper: $('#input_wrapper')
|
||||
},
|
||||
|
||||
// Collection of parameters
|
||||
parameters = {},
|
||||
|
||||
// Adds a new message to the DOM
|
||||
commands = {
|
||||
post: function (type, text, nick) {
|
||||
var tpl = templates.post[type],
|
||||
uniqueId = 'msg_' + new Date().getTime() + '_' + Math.round(Math.random()*1000000),
|
||||
post,
|
||||
data = Object.assign({}, settings, {
|
||||
nick: nick,
|
||||
timestamp: new Date().toLocaleTimeString(),
|
||||
id: uniqueId
|
||||
});
|
||||
|
||||
data.text = $.template(text, data);
|
||||
post = $.template(tpl, data);
|
||||
|
||||
// Request a notification
|
||||
commands.showNotification(type, nick, text);
|
||||
|
||||
// Expire message
|
||||
setTimeout(function() {
|
||||
var parent = components.chat.first(),
|
||||
child = $('#'+uniqueId).first();
|
||||
parent.removeChild(child);
|
||||
}, settings.ttl);
|
||||
|
||||
// Append the post to the chat DOM element
|
||||
components.chat.append(post);
|
||||
},
|
||||
|
||||
torch: function (ttl) {
|
||||
ttl = parseInt(ttl, 10);
|
||||
if( ttl > 0 && ttl < 3600) {
|
||||
mediator.emit('console:info', $.template(templates.messages.torch_is_now, { ttl: ttl }) );
|
||||
settings.ttl = ttl*1000;
|
||||
} else {
|
||||
mediator.emit('console:error', $.template(templates.messages.torch_not_set) );
|
||||
}
|
||||
},
|
||||
|
||||
param: function (p) {
|
||||
parameters = Object.assign({}, parameters, p);
|
||||
},
|
||||
|
||||
showNotification: function (type, nick, text) {
|
||||
var title = (type !== 'message' ? 'Cryptalk' : nick),
|
||||
icon = (type === 'message' ? 'gfx/icon_128x128.png' : (type === 'error' ? 'gfx/icon_128x128_error.png' : 'gfx/icon_128x128_info.png'));
|
||||
|
||||
// Emit notification
|
||||
mediator.emit('notification:send', {
|
||||
title: title.substring(0, 20),
|
||||
body: text.substring(0, 80),
|
||||
icon: icon
|
||||
});
|
||||
|
||||
// Emit sound
|
||||
if (type === 'message') {
|
||||
mediator.emit('audio:play', sounds.message);
|
||||
}
|
||||
},
|
||||
|
||||
motd: function (message) {
|
||||
commands.post('motd', message);
|
||||
},
|
||||
|
||||
info: function (message) {
|
||||
commands.post('info', message);
|
||||
},
|
||||
|
||||
error: function (message) {
|
||||
commands.post('error', message);
|
||||
},
|
||||
|
||||
server: function (message) {
|
||||
commands.post('server', message);
|
||||
},
|
||||
|
||||
message: function (data) {
|
||||
commands.post('message', data.message, data.nick);
|
||||
},
|
||||
|
||||
clearInput: function () {
|
||||
components.input[0].value = '';
|
||||
},
|
||||
|
||||
clear: function () {
|
||||
components.chat[0].innerHTML = '';
|
||||
},
|
||||
|
||||
lockInput: function () {
|
||||
components.input[0].setAttribute('disabled', 'disabled');
|
||||
components.inputWrapper[0].className = 'loading';
|
||||
},
|
||||
|
||||
unlockInput: function () {
|
||||
components.input[0].removeAttribute('disabled');
|
||||
components.inputWrapper[0].className = '';
|
||||
components.input.focus();
|
||||
}
|
||||
},
|
||||
|
||||
// Handler for the document`s keyDown-event.
|
||||
onKeyDown = function (e) {
|
||||
var buffer,
|
||||
parts,
|
||||
payload,
|
||||
command;
|
||||
|
||||
// The Document object is bound to this element.
|
||||
// If the active element is not the input, focus on it and exit the function.
|
||||
// Ignore this when ctrl and/or alt is pressed!
|
||||
if (!e.ctrlKey && !e.altKey && components.input[0] !== $.activeElement()) {
|
||||
components.input.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Return immediatly if the buffer is empty or if the hit key was not <enter>
|
||||
if (e.keyCode !== 13 || !(buffer = components.input[0].value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle command
|
||||
if ((buffer[0] || buffer.slice(0, 1)) === '/') {
|
||||
parts = $.ssplit(buffer.slice(1), ' ');
|
||||
command = parts[0];
|
||||
payload = parts[1];
|
||||
|
||||
// Shout this command to all modules
|
||||
mediator.emit(
|
||||
'command:' + command,
|
||||
payload,
|
||||
function(retvals, recipients) {
|
||||
if(!recipients) {
|
||||
commands.post('error', $.template(templates.messages.unrecognized_command, { commandName: command }));
|
||||
return;
|
||||
} else {
|
||||
commands.clearInput();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
} else /* Handle ordinary message */ {
|
||||
|
||||
if(!parameters.room || !parameters.key ) {
|
||||
// Make sure that the user has joined a room and the key is set
|
||||
return (!parameters.room) ? commands.post('error', templates.messages.msg_no_room) : commands.post('error', templates.messages.msg_no_key);
|
||||
}
|
||||
|
||||
// Before sending the message.
|
||||
// Encrypt message using room UUID as salt and key as pepper.
|
||||
mediator.emit(
|
||||
'socket:emit',
|
||||
{
|
||||
data: 'message:send',
|
||||
payload: {
|
||||
room: $.SHA1(parameters.room),
|
||||
msg: $.AES.encrypt(buffer, $.SHA1(parameters.room) + parameters.key).toString(),
|
||||
nick: parameters.nick ? $.AES.encrypt(parameters.nick, $.SHA1(parameters.room) + parameters.key).toString() : false
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// And clear the the buffer
|
||||
commands.clearInput();
|
||||
}
|
||||
};
|
||||
|
||||
// Bind the necessary DOM events
|
||||
$(document).on('keydown', onKeyDown);
|
||||
|
||||
// Put focus on the message input
|
||||
components.input.focus();
|
||||
|
||||
// Connect events
|
||||
for (var commandName in commands) {
|
||||
if (commandName !== 'post') {
|
||||
mediator.on('console:' + commandName, commands[commandName]);
|
||||
}
|
||||
}
|
||||
|
||||
mediator.on('console:post', function (data) {
|
||||
commands.post(data.type, data.data, data.nick);
|
||||
});
|
||||
}
|
82
client/source/cryptalk.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
import mediator from './vendor/castrato.js';
|
||||
import win from './window.js';
|
||||
import notifications from './notifications.js';
|
||||
import templates from './templates.js';
|
||||
import settings from './settings.js';
|
||||
import host from './host.js';
|
||||
import client from './client.js';
|
||||
import cons from './console.js';
|
||||
import room from './room.js';
|
||||
import sounds from './sounds.js';
|
||||
|
||||
// Inititalize modules
|
||||
let wind = win(mediator);
|
||||
cons(mediator, settings, templates,sounds);
|
||||
notifications(mediator, settings, wind);
|
||||
client(mediator, settings, templates);
|
||||
host(mediator, settings, templates);
|
||||
room(mediator, settings, templates);
|
||||
|
||||
// Mediate between modules
|
||||
mediator
|
||||
.on('window:focused', function() {
|
||||
mediator.emit('audio:off');
|
||||
mediator.emit('notification:off');
|
||||
})
|
||||
|
||||
.on('window:blurred',function() {
|
||||
mediator.emit('audio:on');
|
||||
mediator.emit('notification:on');
|
||||
})
|
||||
|
||||
.on('command:mute', function () {
|
||||
mediator.emit('audio:mute');
|
||||
})
|
||||
|
||||
.on('command:unmute', function () {
|
||||
mediator.emit('audio:unmute');
|
||||
})
|
||||
|
||||
// Help console and host keep track of current states
|
||||
.on('room:changed', function(room) {
|
||||
mediator
|
||||
.emit('console:param', {
|
||||
room: room
|
||||
})
|
||||
.emit('host:param', {
|
||||
room: room
|
||||
});
|
||||
})
|
||||
|
||||
.on('nick:changed', function(nick) {
|
||||
mediator.emit('console:param', {
|
||||
nick: nick
|
||||
});
|
||||
})
|
||||
|
||||
.on('key:changed', function(key) {
|
||||
mediator
|
||||
.emit('console:param', {
|
||||
key: key
|
||||
})
|
||||
.emit('host:param', {
|
||||
key: key
|
||||
});
|
||||
});
|
||||
|
||||
// Connect to the default host
|
||||
mediator.emit('command:connect', undefined, function() {
|
||||
// Join room and set key if a hash in the format #Room:Key has been provided
|
||||
var hash = window.location.hash;
|
||||
if ( hash ) {
|
||||
var parts = hash.slice(1).split(':');
|
||||
|
||||
if ( parts[0] ) {
|
||||
mediator.emit('command:join', parts[0]);
|
||||
}
|
||||
|
||||
if ( parts[1] ) {
|
||||
mediator.emit('command:key', parts[1]);
|
||||
}
|
||||
}
|
||||
});
|
15
client/source/debug.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
export default function (mediator, debug) {
|
||||
if (debug) {
|
||||
mediator.on('*', function (data, done, name) {
|
||||
if (name !== 'console:post' && name !== 'notification:send') {
|
||||
mediator.emit('console:post', {
|
||||
type: 'server',
|
||||
data: name + (data ? '(' + JSON.stringify(data) + ')' : ''),
|
||||
debug: 1
|
||||
});
|
||||
}
|
||||
|
||||
done();
|
||||
});
|
||||
}
|
||||
}
|
180
client/source/host.js
Normal file
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
|
||||
Accepts:
|
||||
mediator.on('command:host', host);
|
||||
mediator.on('command:connect', connect);
|
||||
mediator.on('command:disconnect', disconnect);
|
||||
mediator.on('command:reconnect', disconnect);
|
||||
|
||||
Emits:
|
||||
mediator.on('socket:emit', emit);
|
||||
|
||||
eslint no-console: ["error", { allow: ["warn", "error"] }]
|
||||
|
||||
*/
|
||||
import $ from './$.js';
|
||||
|
||||
export default function (mediator, settings, templates) {
|
||||
|
||||
var
|
||||
// Private properties
|
||||
socket,
|
||||
host = {
|
||||
host: '',
|
||||
connected: false
|
||||
},
|
||||
|
||||
// Collection of parameters
|
||||
parameters = {},
|
||||
|
||||
emit = function(payload) {
|
||||
// Route message from mediator to socket
|
||||
if(socket) socket.emit(payload.data,payload.payload);
|
||||
},
|
||||
|
||||
hostInfo = function () {
|
||||
mediator.emit('info', JSON.stringify(host || {}));
|
||||
},
|
||||
|
||||
connect = function (toHost, done) {
|
||||
|
||||
mediator.emit('console:lockInput');
|
||||
|
||||
if (host && host.connected) {
|
||||
mediator.emit('console:error', $.template(templates.messages.already_connected, {
|
||||
host: host.host
|
||||
}));
|
||||
mediator.emit('console:unlockInput');
|
||||
return;
|
||||
}
|
||||
|
||||
// Push 'Connecting...' message
|
||||
mediator.emit('console:info', $.template(templates.messages.connecting, {host: host.host}));
|
||||
|
||||
// Show motd (placed here to enable server specific motds in future)
|
||||
mediator.emit('console:motd', settings.motd);
|
||||
|
||||
// The one and only socket
|
||||
socket = $.io(host.host, {
|
||||
forceNew: true,
|
||||
'force new connection': true
|
||||
});
|
||||
|
||||
// Bind socket events
|
||||
socket
|
||||
.on('room:joined', function () {
|
||||
|
||||
mediator.emit('console:info', $.template(templates.messages.joined_room, { roomName: $.escapeHtml(parameters.room) } ));
|
||||
|
||||
// Automatically count persons on join
|
||||
socket.emit('room:count');
|
||||
})
|
||||
.on('room:left', function () {
|
||||
mediator.emit('console:info', $.template(templates.messages.left_room, { roomName: $.escapeHtml(parameters.room) } ));
|
||||
mediator.emit('room:changed',false);
|
||||
})
|
||||
|
||||
.on('message:send', function (data) {
|
||||
var decrypted = $.AES.decrypt(data.msg, $.SHA1(parameters.room) + parameters.key),
|
||||
sanitized = $.escapeHtml(decrypted),
|
||||
nick = !data.nick ? templates.default_nick : $.escapeHtml($.AES.decrypt(data.nick, $.SHA1(parameters.room) + parameters.key));
|
||||
|
||||
if (!decrypted) {
|
||||
mediator.emit('console:error', templates.messages.unable_to_decrypt);
|
||||
} else {
|
||||
mediator.emit('console:message', { message: sanitized, nick: nick } );
|
||||
}
|
||||
})
|
||||
|
||||
.on('message:server', function (data) {
|
||||
if( data.msg ) {
|
||||
var sanitized = $.escapeHtml(data.msg);
|
||||
if( templates.server[sanitized] ) {
|
||||
if( data.payload !== undefined ) {
|
||||
var sanitized_payload = $.escapeHtml(data.payload);
|
||||
mediator.emit('console:server', $.template(templates.server[sanitized], { payload: sanitized_payload }));
|
||||
} else {
|
||||
mediator.emit('console:server', templates.server[sanitized]);
|
||||
}
|
||||
} else {
|
||||
mediator.emit('console:error', templates.server.bogus);
|
||||
}
|
||||
} else {
|
||||
mediator.emit('console:error', templates.server.bogus);
|
||||
}
|
||||
})
|
||||
|
||||
.on('connect', function () {
|
||||
|
||||
// Tell the user that the chat is ready to interact with
|
||||
mediator.emit('console:info', $.template(templates.messages.connected, {
|
||||
host: host.host
|
||||
}));
|
||||
|
||||
// Set window title
|
||||
mediator.emit('window:title', settings.title);
|
||||
|
||||
// Unlock input
|
||||
mediator.emit('console:unlockInput');
|
||||
|
||||
done();
|
||||
|
||||
host.connected = true;
|
||||
})
|
||||
|
||||
.on('disconnect', function () {
|
||||
|
||||
host.connected = false;
|
||||
|
||||
// Tell the user that the chat is ready to interact with
|
||||
mediator.emit('console:info', $.template(templates.messages.disconnected, {
|
||||
host: host.host
|
||||
}));
|
||||
|
||||
// Revert title
|
||||
mediator.emit('room:changed',undefined);
|
||||
mediator.emit('window:title',templates.client.title);
|
||||
})
|
||||
|
||||
.on('connect_error', function () {
|
||||
|
||||
host.connected = false;
|
||||
mediator.emit('console:error', templates.messages.socket_error);
|
||||
|
||||
// Unlock input
|
||||
mediator.emit('console:unlockInput');
|
||||
});
|
||||
|
||||
return;
|
||||
},
|
||||
|
||||
reconnect = function (foo, done) {
|
||||
if (host) {
|
||||
if (host.connected) {
|
||||
disconnect();
|
||||
connect(host, done);
|
||||
} else {
|
||||
connect(host, done);
|
||||
}
|
||||
} else {
|
||||
done();
|
||||
return mediator.emit('console:error', templates.messages.reconnect_no_host);
|
||||
}
|
||||
},
|
||||
|
||||
disconnect = function () {
|
||||
socket.disconnect();
|
||||
},
|
||||
|
||||
param = function (p) {
|
||||
parameters = Object.assign({}, parameters, p );
|
||||
};
|
||||
|
||||
mediator.on('command:host', hostInfo);
|
||||
mediator.on('command:connect', connect);
|
||||
mediator.on('command:disconnect', disconnect);
|
||||
mediator.on('command:reconnect', reconnect);
|
||||
|
||||
mediator.on('socket:emit', emit);
|
||||
mediator.on('host:param', param);
|
||||
}
|
|
@ -1,115 +1,125 @@
|
|||
/*
|
||||
Usage
|
||||
|
||||
// Send an notification
|
||||
mediator.emit('notification:send',{
|
||||
title: 'Woop',
|
||||
body: 'Woop woop',
|
||||
icon: 'gfx/icon.png'
|
||||
});
|
||||
|
||||
// Turn notifications on
|
||||
mediator.emit('notification:on');
|
||||
|
||||
// Turn notifications off
|
||||
mediator.emit('notification:off');
|
||||
|
||||
*/
|
||||
define(['mediator','win'], function (mediator, win) {
|
||||
var enabled = true,
|
||||
|
||||
native_supported = false,
|
||||
|
||||
new_title,
|
||||
original_title,
|
||||
blink_timer,
|
||||
interval,
|
||||
|
||||
now = function () {
|
||||
return performance.now() || Date.now();
|
||||
},
|
||||
|
||||
on = function () {
|
||||
enabled = true;
|
||||
},
|
||||
|
||||
off = function () {
|
||||
enabled = false;
|
||||
},
|
||||
|
||||
resetState = function() {
|
||||
clearTimeout(blink_timer);
|
||||
if (original_title !== undefined) setTitle(original_title);
|
||||
original_title = undefined;
|
||||
new_title = undefined;
|
||||
window_active = true;
|
||||
},
|
||||
|
||||
doBlink = function() {
|
||||
if(enabled) {
|
||||
if( win.getTitle() == original_title )
|
||||
win.setTitle( new_title );
|
||||
else
|
||||
win.setTitle( original_title);
|
||||
|
||||
blink_timer = setTimeout(doBlink,interval);
|
||||
} else {
|
||||
resetState();
|
||||
}
|
||||
},
|
||||
|
||||
enableNative = function() {
|
||||
if( native_supported && Notification.permission !== 'denied' ) {
|
||||
Notification.requestPermission(function (status) {
|
||||
Notification.permission = status;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
blinkTitleUntilFocus = function(t,i) {
|
||||
interval = (i == undefined) ? 1000 : i;
|
||||
if ( enabled ) {
|
||||
new_title = t;
|
||||
original_title = getTitle();
|
||||
doBlink();
|
||||
}
|
||||
},
|
||||
|
||||
notify = function(title,body,icon,fallback) {
|
||||
// Only notify while in background
|
||||
if( enabled) {
|
||||
|
||||
// Set default value for fallback parameter
|
||||
if ( fallback === undefined) fallback = false;
|
||||
|
||||
if ( native_supported && Notification.permission === "granted") {
|
||||
|
||||
// Create notification
|
||||
var n = new Notification(title, {body: body, icon:icon});
|
||||
|
||||
// Handle on show event
|
||||
n.onshow = function () {
|
||||
// Automatically close the notification after 5000ms
|
||||
setTimeout(function(){n.close();},3000);
|
||||
}
|
||||
|
||||
} else if ( fallback ) {
|
||||
exports.blinkTitleUntilFocus("Attention",1000);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
native_supported = (window.Notification !== undefined);
|
||||
|
||||
mediator.on('notification:send',function(data) { notify(data.title,data.body,data.icon,true); });
|
||||
mediator.on('notification:on',function() { on(); });
|
||||
mediator.on('notification:off',function() { off(); });
|
||||
|
||||
enableNative();
|
||||
|
||||
off();
|
||||
|
||||
// Make sure we are at square one
|
||||
resetState();
|
||||
|
||||
});
|
||||
/*
|
||||
Usage
|
||||
|
||||
// Send an notification
|
||||
mediator.emit('notification:send',{
|
||||
title: 'Woop',
|
||||
body: 'Woop woop',
|
||||
icon: 'gfx/icon.png'
|
||||
});
|
||||
|
||||
// Turn notifications on
|
||||
mediator.emit('notification:on');
|
||||
|
||||
// Turn notifications off
|
||||
mediator.emit('notification:off');
|
||||
|
||||
*/
|
||||
|
||||
|
||||
export default function(mediator, settings, win) {
|
||||
|
||||
var enabled = true,
|
||||
|
||||
native_supported = false,
|
||||
|
||||
new_title,
|
||||
original_title,
|
||||
blink_timer,
|
||||
interval,
|
||||
|
||||
last,
|
||||
|
||||
now = function () {
|
||||
return performance.now() || Date.now();
|
||||
},
|
||||
|
||||
on = function () {
|
||||
enabled = true;
|
||||
},
|
||||
|
||||
off = function () {
|
||||
enabled = false;
|
||||
},
|
||||
|
||||
resetState = function() {
|
||||
clearTimeout(blink_timer);
|
||||
if (original_title !== undefined) win.setTitle(original_title);
|
||||
original_title = undefined;
|
||||
new_title = undefined;
|
||||
},
|
||||
|
||||
doBlink = function() {
|
||||
if(enabled) {
|
||||
if( win.getTitle() === original_title )
|
||||
win.setTitle( new_title );
|
||||
else
|
||||
win.setTitle( original_title);
|
||||
|
||||
blink_timer = setTimeout(doBlink,interval);
|
||||
} else {
|
||||
resetState();
|
||||
}
|
||||
},
|
||||
|
||||
enableNative = function() {
|
||||
if( native_supported && Notification.permission !== 'denied' ) {
|
||||
Notification.requestPermission();
|
||||
}
|
||||
},
|
||||
|
||||
blinkTitleUntilFocus = function(t,i) {
|
||||
interval = (i === undefined) ? 1000 : i;
|
||||
if ( enabled && original_title === undefined ) {
|
||||
new_title = t;
|
||||
original_title = win.getTitle();
|
||||
doBlink();
|
||||
}
|
||||
},
|
||||
|
||||
notify = function(title,body,icon,fallback) {
|
||||
|
||||
// Only notify while in background, and if sufficient time has passed
|
||||
if( enabled && (now() - last) > settings.notifications.maxOnePerMs ) {
|
||||
|
||||
// Set default value for fallback parameter
|
||||
if ( fallback === undefined) fallback = false;
|
||||
|
||||
if ( native_supported && Notification.permission === 'granted') {
|
||||
|
||||
// Create notification
|
||||
var n = new Notification(title, {body: body, icon:icon});
|
||||
|
||||
// Handle on show event
|
||||
n.onshow = function () {
|
||||
// Automatically close the notification after 5000ms
|
||||
setTimeout(function(){n.close();},3000);
|
||||
};
|
||||
|
||||
last = now();
|
||||
|
||||
} else if ( fallback ) {
|
||||
blinkTitleUntilFocus('Attention', 1000);
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
native_supported = (window.Notification !== undefined);
|
||||
|
||||
mediator.on('notification:send',function(data) { notify(data.title,data.body,data.icon,true); });
|
||||
mediator.on('notification:on',function() { on(); });
|
||||
mediator.on('notification:off',function() { off(); });
|
||||
|
||||
// Always enable native notifications
|
||||
enableNative();
|
||||
|
||||
// Start with notifications disabled
|
||||
off();
|
||||
|
||||
// If this is undefined, notifications will fail to show
|
||||
last = now();
|
||||
|
||||
// Make sure we are at square one
|
||||
resetState();
|
||||
}
|
42
client/source/queue.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
var exports = {},
|
||||
queue = [],
|
||||
now = function () {
|
||||
return performance.now() || Date.now();
|
||||
};
|
||||
|
||||
exports.add_function_delayed = function(delay, callback, data) {
|
||||
queue.push({
|
||||
func: callback,
|
||||
pushed: now(),
|
||||
delay: delay,
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
exports.get = function () {
|
||||
return queue;
|
||||
};
|
||||
|
||||
exports.run = function () {
|
||||
var i = 0,
|
||||
current,
|
||||
lrt_inner;
|
||||
|
||||
while ((current = queue[i++])) {
|
||||
if (now() - current.pushed > current.delay) {
|
||||
current.func();
|
||||
queue.splice(i - 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (queue.length) {
|
||||
// Waste a ms to prevent callstack overflow
|
||||
lrt_inner = now();
|
||||
|
||||
while (now() - lrt_inner < 1) { void 0; }
|
||||
|
||||
exports.run();
|
||||
}
|
||||
};
|
||||
|
||||
export default exports;
|
69
client/source/room.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
|
||||
Accepts:
|
||||
mediator.on('command:join', ...);
|
||||
mediator.on('command:leave', ...);
|
||||
mediator.on('command:key', ...);
|
||||
|
||||
Emits:
|
||||
mediator.emit('room:changed',...);
|
||||
mediator.emit('console:error',...);
|
||||
mediator.emit('socket:emit',...);
|
||||
|
||||
*/
|
||||
|
||||
import $ from './$.js';
|
||||
|
||||
export default function (mediator, settings, templates) {
|
||||
var // Private properties
|
||||
room = false,
|
||||
|
||||
join = function(payload) {
|
||||
if (room !== false) {
|
||||
mediator.emit('console:error',
|
||||
$.template(templates.messages.already_in_room, {
|
||||
room: room
|
||||
})
|
||||
);
|
||||
} else if (payload.length >= settings.room.maxLen) {
|
||||
mediator.emit('console:error', $.template(templates.messages.room_name_too_long));
|
||||
} else if (payload.length < settings.room.minLen) {
|
||||
mediator.emit('console:error', $.template(templates.messages.room_name_too_short));
|
||||
} else {
|
||||
room = payload;
|
||||
|
||||
mediator
|
||||
.emit('room:changed', room)
|
||||
.emit('socket:emit', {
|
||||
data: 'room:join',
|
||||
payload: $.SHA1(room)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
leave = function() {
|
||||
if (room !== false) {
|
||||
mediator.emit('socket:emit', {
|
||||
data: 'room:leave',
|
||||
payload: $.SHA1(room)
|
||||
});
|
||||
|
||||
room = false;
|
||||
} else {
|
||||
mediator.emit('console:error', templates.messages.leave_from_nowhere);
|
||||
}
|
||||
},
|
||||
|
||||
count = function() {
|
||||
if (room) {
|
||||
mediator.emit('socket:emit', {data: 'room:count'});
|
||||
} else {
|
||||
mediator.emit('console:error', templates.messages.not_in_room);
|
||||
}
|
||||
};
|
||||
|
||||
// Connect events
|
||||
mediator.on('command:join', join);
|
||||
mediator.on('command:leave', leave);
|
||||
mediator.on('command:count', count);
|
||||
}
|
34
client/source/settings.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
export default {
|
||||
|
||||
title: 'Claytonia Chat',
|
||||
|
||||
ttl: 600000,
|
||||
|
||||
motd: '<pre>\n\n' +
|
||||
' Welcome to Claytonia Chat \n' +
|
||||
' Tip of the day: /help \n' +
|
||||
' Public Room: /join Claytonia \n' +
|
||||
' Public Key: /key Claytonia \n' +
|
||||
' Everyone in the room must have the same key to decrypt messages. \n' +
|
||||
'----------------------------------------------------------------------' +
|
||||
'</pre>',
|
||||
|
||||
nick: {
|
||||
maxLen: 20,
|
||||
minLen: 2,
|
||||
},
|
||||
|
||||
key: {
|
||||
maxLen: 1024,
|
||||
minLen: 8,
|
||||
},
|
||||
|
||||
room: {
|
||||
minLen: 1,
|
||||
maxLen: 64
|
||||
},
|
||||
|
||||
notifications: {
|
||||
maxOnePerMs: 3000
|
||||
}
|
||||
};
|
|
@ -1,20 +1,20 @@
|
|||
define({
|
||||
message: [
|
||||
[261.63*2,0,50],
|
||||
[261.63*3,0,50],
|
||||
[261.63*4,50,50],
|
||||
[261.63*5,50,50]
|
||||
],
|
||||
person_joined: [
|
||||
[261.63*3,0,200],
|
||||
[261.63*1,0,200],
|
||||
[261.63*3,200,500],
|
||||
[261.63*2,200,500]
|
||||
],
|
||||
person_left: [
|
||||
[261.63*3,0,200],
|
||||
[261.63*2,0,200],
|
||||
[261.63*3,200,500],
|
||||
[261.63*1,200,500]
|
||||
]
|
||||
});
|
||||
export default {
|
||||
message: [
|
||||
[261.63*2,0,50],
|
||||
[261.63*3,0,50],
|
||||
[261.63*4,50,50],
|
||||
[261.63*5,50,50]
|
||||
],
|
||||
person_joined: [
|
||||
[261.63*3,0,200],
|
||||
[261.63*1,0,200],
|
||||
[261.63*3,200,500],
|
||||
[261.63*2,200,500]
|
||||
],
|
||||
person_left: [
|
||||
[261.63*3,0,200],
|
||||
[261.63*2,0,200],
|
||||
[261.63*3,200,500],
|
||||
[261.63*1,200,500]
|
||||
]
|
||||
};
|
|
@ -1,22 +1,6 @@
|
|||
// The templating function only supports variables.
|
||||
// Define a variable as so: {variable_name}
|
||||
define({
|
||||
motd: '<pre>\n\n' +
|
||||
'▄████▄ ██▀███ ▓██ ██▓ ██▓███ ▄▄▄█████▓ ▄▄▄ ██▓ ██ ▄█▀ \n' +
|
||||
'▒██▀ ▀█ ▓██ ▒ ██▒▒██ ██▒▓██░ ██▒▓ ██▒ ▓▒▒████▄ ▓██▒ ██▄█▒ \n' +
|
||||
'▒▓█ ▄ ▓██ ░▄█ ▒ ▒██ ██░▓██░ ██▓▒▒ ▓██░ ▒░▒██ ▀█▄ ▒██░ ▓███▄░ \n' +
|
||||
'▒▓▓▄ ▄██▒▒██▀▀█▄ ░ ▐██▓░▒██▄█▓▒ ▒░ ▓██▓ ░ ░██▄▄▄▄██ ▒██░ ▓██ █▄ \n' +
|
||||
'▒ ▓███▀ ░░██▓ ▒██▒ ░ ██▒▓░▒██▒ ░ ░ ▒██▒ ░ ▓█ ▓██▒░██████▒▒██▒ █▄ \n' +
|
||||
'░ ░▒ ▒ ░░ ▒▓ ░▒▓░ ██▒▒▒ ▒▓▒░ ░ ░ ▒ ░░ ▒▒ ▓▒█░░ ▒░▓ ░▒ ▒▒ ▓▒ \n' +
|
||||
' ░ ▒ ░▒ ░ ▒░▓██ ░▒░ ░▒ ░ ░ ▒ ▒▒ ░░ ░ ▒ ░░ ░▒ ▒░ \n' +
|
||||
'░ ░░ ░ ▒ ▒ ░░ ░░ ░ ░ ▒ ░ ░ ░ ░░ ░ \n' +
|
||||
'░ ░ ░ ░ ░ ░ ░ ░ ░░ ░ \n' +
|
||||
'░ ░ ░ \n' +
|
||||
' https://github.com/hexagon/cryptalk \n' +
|
||||
' \n' +
|
||||
' Tip of the day: /help \n' +
|
||||
'----------------------------------------------------------------------' +
|
||||
'</pre>',
|
||||
export default {
|
||||
|
||||
help: '<pre> \n' +
|
||||
'Cryptalk, encrypted instant chat. \n' +
|
||||
|
@ -31,16 +15,17 @@ define({
|
|||
' /clear Clear on-screen buffer \n' +
|
||||
' /help This \n' +
|
||||
' /title Set your local page title \n' +
|
||||
' /torch AfterSeconds Console messages are torched \n' +
|
||||
' after this amount of seconds \n' +
|
||||
' (default 600). \n' +
|
||||
' \n' +
|
||||
'Room: \n' +
|
||||
' /generate Generate random room \n' +
|
||||
' /join RoomId Join a room \n' +
|
||||
' /leave Leave the room \n' +
|
||||
' /count Count participants \n' +
|
||||
' \n' +
|
||||
'Host: \n' +
|
||||
' /hosts List available hosts \n' +
|
||||
' /connect HostIndex Connect to selected host \n' +
|
||||
' /connect Connect to host \n' +
|
||||
' /disconnect Disconnect from host \n' +
|
||||
' \n' +
|
||||
'You can select any of the five last commands/messages with up/down key.\n' +
|
||||
|
@ -59,11 +44,11 @@ define({
|
|||
// All post templates will have access to the properties in the 'settings' module,
|
||||
// along with the current nick, room, mute-status and of course the message ('text').
|
||||
post: {
|
||||
motd: '<li><i class="motd">{text}</i></li>',
|
||||
info: '<li>INF> <i class="info">{text}</i></li>',
|
||||
server: '<li>SRV> <i class="server">{text}</i></li>',
|
||||
error: '<li>ERR> <i class="error">{text}</i></li>',
|
||||
message: '<li><i class="nick">{nick}></i> <i class="message">{text}</i></li>'
|
||||
motd: '<li id="{id}"><i class="motd">{text}</i></li>',
|
||||
info: '<li id="{id}"><i class="timestamp">[{timestamp}] </i>INF> <i class="info">{text}</i></li>',
|
||||
server: '<li id="{id}"><i class="timestamp">[{timestamp}] </i>SRV> <i class="server">{text}</i></li>',
|
||||
error: '<li id="{id}"><i class="timestamp">[{timestamp}] </i>ERR> <i class="error">{text}</i></li>',
|
||||
message: '<li id="{id}"><i class="timestamp">[{timestamp}] </i>MSG> <i class="nick">{nick}></i> <i class="message">{text}</i></li>'
|
||||
},
|
||||
|
||||
// All message templates will have access to the properties in the 'settings' module,
|
||||
|
@ -71,8 +56,7 @@ define({
|
|||
messages: {
|
||||
key_to_short: 'Hmm, that\'s a weak key, try again...',
|
||||
key_to_long: 'Man that\'s a long key. Make it a tad short, \'kay?',
|
||||
key_ok_ready: 'Key set, you can now start communicating.',
|
||||
key_ok_but_no_room: 'Key set, you can now join a room and start communicating.',
|
||||
key_ok: 'Key set, you can now start communicating.',
|
||||
key_no_host: 'You have to connect to a host before setting the key.',
|
||||
|
||||
join_no_host: 'You have to connect to a host before joining a room.',
|
||||
|
@ -86,6 +70,9 @@ define({
|
|||
msg_no_key: 'You have to set an encryption key before sending a message. See /help.',
|
||||
leave_from_nowhere: 'How are you supposed to leave, while being nowhere?',
|
||||
|
||||
torch_is_now: 'Messages are now torched after {ttl} seconds.',
|
||||
torch_not_set: 'Invalid torch delay entered, nothing changed. See /help.',
|
||||
|
||||
title_set: 'The title of this window is now \'{title}\'.',
|
||||
|
||||
muted: 'Notifications and sounds are now muted.',
|
||||
|
@ -93,9 +80,12 @@ define({
|
|||
|
||||
unrecognized_command: 'Unrecognized command: "{commandName}"',
|
||||
|
||||
joined_room: 'Joined room {room}',
|
||||
left_room: 'Left room {room}',
|
||||
already_in_room: 'You are already in a room ({room}), stoopid.',
|
||||
room_name_too_long: 'Isn\'t that a bit long?',
|
||||
room_name_too_short: 'Nah, too short.',
|
||||
|
||||
joined_room: 'Joined room {roomName}.',
|
||||
left_room: 'Left room {roomName}.',
|
||||
already_in_room: 'You are already in a room ({room}), try /leave first.',
|
||||
|
||||
unable_to_decrypt: 'Unabled to decrypt received message, keys does not match.',
|
||||
|
||||
|
@ -113,9 +103,13 @@ define({
|
|||
server: {
|
||||
person_joined: 'A person joined this room.',
|
||||
person_left: 'A person left this room.',
|
||||
room_generated: 'Room {payload} generated.',
|
||||
person_count: 'There is {payload} person(s) in this room, including you.',
|
||||
person_count: 'There are {payload} people in this room, including you.',
|
||||
person_single: 'You are the only person in this room.',
|
||||
command_failed: 'Server command failed, you\'re probably trying to du something bogus.',
|
||||
bogus: 'Received a bogus message from server.'
|
||||
},
|
||||
|
||||
client: {
|
||||
title: 'Cryptalk - Offline'
|
||||
}
|
||||
});
|
||||
};
|
300
client/source/vendor/castrato.js
vendored
Normal file
|
@ -0,0 +1,300 @@
|
|||
/**
|
||||
* Licensed under the MIT License
|
||||
*
|
||||
* Copyright (c) 2014 Pehr Boman (github.com/unkelpehr)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/** @license Licenced under MIT - castrato - ©2014 Pehr Boman <github.com/unkelpehr> */
|
||||
|
||||
let
|
||||
/**
|
||||
* Contains the next unique node id.
|
||||
*
|
||||
* @property index
|
||||
* @type {Integer}
|
||||
* @private
|
||||
*/
|
||||
index = 0,
|
||||
|
||||
/**
|
||||
* Contains all subscriptions
|
||||
*
|
||||
* @property subs
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
subs = {},
|
||||
|
||||
/**
|
||||
* Contains all emits that has been done with the `persistent` parameter set to `true`.
|
||||
*
|
||||
* @property emits
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
emits = {},
|
||||
|
||||
/**
|
||||
* An empty function that does not accept any arguments.
|
||||
*
|
||||
* @property noop
|
||||
* @type {function}
|
||||
* @private
|
||||
*/
|
||||
noop = function () {};
|
||||
|
||||
/**
|
||||
* Creates a new entry in the `subs` object.
|
||||
*
|
||||
* @method on
|
||||
* @private
|
||||
* @param {Integer} fromId The unique subscriber ID.
|
||||
* @param {String} event The event to subscribe to.
|
||||
* @param {Function} handler A function to execute when the event is triggered.
|
||||
*/
|
||||
function on (fromId, event, handler, once) {
|
||||
let i, item, subscription = [fromId, handler, handler.length > 1];
|
||||
|
||||
// Create if needed a namespace for this event and push the subscription.
|
||||
(subs[event] || (subs[event] = [])).push(subscription);
|
||||
|
||||
// If it exists a persistent event that matches that which is currently being bound;
|
||||
// loop through and each of them and emit to this handler.
|
||||
if (emits[event]) {
|
||||
i = 0;
|
||||
subscription = [subscription];
|
||||
while ((item = emits[event][i++])) {
|
||||
emit(
|
||||
0, // `persistent`
|
||||
0, // `event`
|
||||
item[0], // `data`
|
||||
item[1], // `handler`
|
||||
subscription // `explicitSubs`
|
||||
);
|
||||
|
||||
if (once) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event handlers originating from `fromId` and optionally filter by handler.
|
||||
*
|
||||
* @method off
|
||||
* @private
|
||||
* @param {Integer} fromId The unique subscriber ID.
|
||||
* @param {String} event The event to unsubscribe from.
|
||||
* @param {Function} [handler=null] The original handler that was attached to this event. If not passed, all subscriptions will be removed.
|
||||
*/
|
||||
function off (fromId, event, handler) {
|
||||
let sub,
|
||||
i = 0,
|
||||
toSubs = subs[event];
|
||||
|
||||
if (toSubs) {
|
||||
while ((sub = toSubs[i++])) {
|
||||
if (sub[0] === fromId && (!handler || handler === sub[1])) {
|
||||
toSubs.splice(--i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through all subscriptions, calling all handlers attached to given `event`.
|
||||
*
|
||||
* @method emit
|
||||
* @private
|
||||
* @param {Integer} fromId The unique subscriber ID.
|
||||
* @param {String} event The event to emit
|
||||
* @param {Object} [data=undefined] Parameters to pass along to the event handler.
|
||||
* @param {Function} [handler=undefined] A function to execute when all event handlers has returned.
|
||||
*/
|
||||
function emit (persistent, event, data, callback, explicitSubs) {
|
||||
let sub,
|
||||
toSubs = explicitSubs || subs[event] || [],
|
||||
total = toSubs.length,
|
||||
left,
|
||||
loop,
|
||||
answers = [],
|
||||
done;
|
||||
|
||||
// Add any wildcard subscriptions to the target subscriptions.
|
||||
if (subs['*']) {
|
||||
toSubs = toSubs.concat(subs['*']);
|
||||
}
|
||||
|
||||
// Wildcard subscriptions shouldn't be counted as subscribers when passed to a possible emit callback.
|
||||
loop = left = toSubs.length;
|
||||
|
||||
// Don't continue setup for calling all the subscribers if there isn't any.
|
||||
if (loop) {
|
||||
// If the emit function does not include a callback;
|
||||
// we still have to set `done` to `noop` so that event callbacks
|
||||
// does not try to execute something that is not a function.
|
||||
done = !callback ? noop : function (data) {
|
||||
if (data) {
|
||||
answers.push(data);
|
||||
}
|
||||
|
||||
if (!--left) {
|
||||
callback(answers, total);
|
||||
callback = 0;
|
||||
}
|
||||
};
|
||||
|
||||
// Execute all handlers that are bound to this event.
|
||||
// Passing `done` if the handler expects it - otherwise decrementing the `left` variable.
|
||||
while ((sub = toSubs[--loop])) {
|
||||
sub[1](data, sub[2] ? done : left--, event);
|
||||
}
|
||||
}
|
||||
|
||||
// `func` get destructed when called.
|
||||
// It has to be called at least once - even if no one was subscribing.
|
||||
// Execute it if it still exists.
|
||||
if (!left && callback) {
|
||||
callback(answers, total);
|
||||
}
|
||||
|
||||
// Save this emit if the `persistent` parameter is set to `true`.
|
||||
if (persistent) {
|
||||
(emits[event] || (emits[event] = [])).push([data, callback]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Castrato entrypoint
|
||||
*
|
||||
* @constructor
|
||||
* @returns {Castrato}
|
||||
*/
|
||||
function Castrato () {
|
||||
this.nodeId = index++;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute all handlers attached to the given event.
|
||||
*
|
||||
* @method emit
|
||||
* @param {String} event The event to emit
|
||||
* @param {Object} [data=undefined] Parameters to pass along to the event handler.
|
||||
* @param {Function} [func=undefined] A function to execute when all event handlers has returned.
|
||||
* @return {Castrato} `this`
|
||||
* @example
|
||||
* $.emit('something');
|
||||
* $.emit('something', { foo: 'bar' });
|
||||
* $.emit('something', { foo: 'bar' }, function (data, subscribers) {
|
||||
* console.log('Emit done, a total of ' + subscribers + ' subscribers returned: ', data);
|
||||
* });
|
||||
*/
|
||||
Castrato.prototype.emit = function (persistent, event, data, handler) {
|
||||
// emit('something', { data: true }, function () {});
|
||||
if (persistent !== true && persistent !== false) {
|
||||
handler = data;
|
||||
data = event;
|
||||
event = persistent;
|
||||
persistent = false;
|
||||
}
|
||||
|
||||
emit(persistent, event, data, handler);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach an event handler function for an event.
|
||||
*
|
||||
* @method on
|
||||
* @param {String} event The event to subscribe to.
|
||||
* @param {Function} handler A function to execute when the event is triggered.
|
||||
* @return {Castrato} `this`
|
||||
* @example
|
||||
* $.on('something', function (data) {
|
||||
* console.log('Got something!', data);
|
||||
* });
|
||||
*/
|
||||
Castrato.prototype.on = function (event, handler) {
|
||||
on(this.nodeId, event, handler);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Attach an event handler function for an event which will only be fired once.
|
||||
*
|
||||
* @method once
|
||||
* @param {String} event The event to subscribe to.
|
||||
* @param {Function} handler A function to execute when the event is triggered.
|
||||
* @return {Castrato} `this`
|
||||
* @example
|
||||
* $.once('something', function (data) {
|
||||
* console.log('Got something!', data);
|
||||
* });
|
||||
*/
|
||||
Castrato.prototype.once = function (event, handler) {
|
||||
on(this.nodeId, event, function wrapper (data, done) {
|
||||
off(this.nodeId, event, wrapper);
|
||||
handler(data, (handler.length > 1) ? done : done());
|
||||
}, true);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes an event handler function for an event.
|
||||
*
|
||||
* @method off
|
||||
* @param {String} event The event to unsubscribe from.
|
||||
* @param {Function} [handler=null] The original handler that was attached to this event. If not passed, all subscriptions will be removed.
|
||||
* @return {Castrato} `this`
|
||||
* @example
|
||||
* $.off('something');
|
||||
* $.off('something else', handler);
|
||||
*/
|
||||
Castrato.prototype.off = function (event, handler) {
|
||||
off(this.nodeId, event, handler);
|
||||
return this;
|
||||
};
|
||||
|
||||
// Only used in testing.
|
||||
// Should get removed in production (and will be removed in the minified version)
|
||||
Castrato.prototype.destroy = function () {
|
||||
this.nodeId = 0;
|
||||
index = 0;
|
||||
subs = {};
|
||||
emits = {};
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
// Always return instance?
|
||||
/**
|
||||
* @type {Castrato}
|
||||
*/
|
||||
let castrato = new Castrato();
|
||||
|
||||
// Export both named and default
|
||||
export default castrato;
|
||||
export { castrato };
|
|
@ -1,39 +1,42 @@
|
|||
/*
|
||||
|
||||
Emits:
|
||||
'window:focused'
|
||||
'window:blurred'
|
||||
|
||||
Exports:
|
||||
title = window.getTitle();
|
||||
window.setTitle(title);
|
||||
|
||||
*/
|
||||
define(['mediator'],function (mediator){
|
||||
|
||||
var exports = {},
|
||||
|
||||
focusCallback = function() {
|
||||
mediator.emit('window:focused');
|
||||
},
|
||||
|
||||
blurCallback = function() {
|
||||
mediator.emit('window:blurred');
|
||||
};
|
||||
|
||||
exports.setTitle = function(t) { document.title = t; },
|
||||
exports.getTitle = function() { return document.title; };
|
||||
|
||||
// Keep track of document focus/blur
|
||||
if (window.addEventListener){
|
||||
// Normal browsers
|
||||
window.addEventListener("focus", focusCallback, true);
|
||||
window.addEventListener("blur", blurCallback, true);
|
||||
} else {
|
||||
// IE
|
||||
window.observe("focusin", focusCallback);
|
||||
window.observe("focusout", blurCallback);
|
||||
}
|
||||
|
||||
return exports;
|
||||
});
|
||||
/*
|
||||
Accepts:
|
||||
'window:title'
|
||||
|
||||
Emits:
|
||||
'window:focused'
|
||||
'window:blurred'
|
||||
|
||||
Exports:
|
||||
title = window.getTitle();
|
||||
window.setTitle(title);
|
||||
|
||||
*/
|
||||
export default function(mediator) {
|
||||
var exports = {},
|
||||
|
||||
focusCallback = function() {
|
||||
mediator.emit('window:focused');
|
||||
},
|
||||
|
||||
blurCallback = function() {
|
||||
mediator.emit('window:blurred');
|
||||
};
|
||||
|
||||
exports.setTitle = function(t) { document.title = t; },
|
||||
exports.getTitle = function() { return document.title; };
|
||||
|
||||
// Keep track of document focus/blur
|
||||
if (window.addEventListener){
|
||||
// Normal browsers
|
||||
window.addEventListener('focus', focusCallback, true);
|
||||
window.addEventListener('blur', blurCallback, true);
|
||||
} else {
|
||||
// IE
|
||||
window.observe('focusin', focusCallback);
|
||||
window.observe('focusout', blurCallback);
|
||||
}
|
||||
|
||||
mediator.on('window:title',exports.setTitle);
|
||||
|
||||
return exports;
|
||||
}
|
17
cryptalk.service
Normal file
|
@ -0,0 +1,17 @@
|
|||
[Unit]
|
||||
Description=Cryptalk Node.js App
|
||||
Documentation=https://github.com/Hexagon/cryptalk
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/npm run start
|
||||
WorkingDirectory=/home/cryptochat/cryptalk
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
Environment=NODE_ENV=production
|
||||
Environment=PATH=/usr/bin
|
||||
User=cryptochat
|
||||
Group=cryptochat
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
3478
package-lock.json
generated
Normal file
100
package.json
|
@ -1,42 +1,58 @@
|
|||
{
|
||||
"name" : "cryptalk",
|
||||
"version" : "1.0.1",
|
||||
"description" : "Encrypted HTML5/Node.JS instant chat",
|
||||
"main" : "server.js",
|
||||
"subdomain": "cryptalk",
|
||||
"analyze": true,
|
||||
"preferGlobal": false,
|
||||
"private": false,
|
||||
"keywords": [
|
||||
"cryptalk",
|
||||
"fandango",
|
||||
"crypto-js",
|
||||
"AES",
|
||||
"secure",
|
||||
"html5"
|
||||
],
|
||||
"author": "Hexagon <Hexagon@GitHub>",
|
||||
"contributors": [{
|
||||
"name": "Pehr Boman",
|
||||
"email": "unkelpehr@gmail.com"
|
||||
}],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Hexagon/cryptalk.git"
|
||||
},
|
||||
"bin" : {
|
||||
"npm" : "./server.js"
|
||||
|
||||
},
|
||||
"dependencies": {
|
||||
"node-uuid": ">= 1.4.1",
|
||||
"express.io": ">= 1.1.13"
|
||||
},
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux",
|
||||
"win32"
|
||||
],
|
||||
"devDependencies": {}
|
||||
}
|
||||
{
|
||||
"name": "cryptalk",
|
||||
"version": "1.2.9",
|
||||
"description": "Encrypted HTML5/Node.JS instant chat",
|
||||
"main": "server/server.js",
|
||||
"preferGlobal": true,
|
||||
"private": false,
|
||||
"scripts": {
|
||||
"test": "echo \"No tests written yet\" && exit 0",
|
||||
"build": "npm update && npm outdated && npm run test:lint && npx rollup -c rollup.config.js && npm run build:minify && npm run build:cleanup",
|
||||
"build:ci": "npm run test:lint && npx rollup -c rollup.config.js && npm run build:minify && npm run build:cleanup",
|
||||
"build:minify": "uglifyjs client/public/js/cryptalk.js --source-map -o client/public/js/cryptalk.min.js",
|
||||
"build:cleanup": "(rm client/public/js/cryptalk.js || del client\\public\\js\\cryptalk.js)",
|
||||
"test:lint": "eslint ./client/source/**/*.js ./server/*.js",
|
||||
"start": "node ./server/server.js"
|
||||
},
|
||||
"keywords": [
|
||||
"cryptalk",
|
||||
"chat",
|
||||
"crypto-js",
|
||||
"AES",
|
||||
"secure",
|
||||
"html5",
|
||||
"encryption",
|
||||
"privacy",
|
||||
"e2ee"
|
||||
],
|
||||
"author": "Hexagon <Hexagon@GitHub>",
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Pehr Boman",
|
||||
"email": "unkelpehr@gmail.com"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/Hexagon/cryptalk.git"
|
||||
},
|
||||
"bin": "./server/server.js",
|
||||
"dependencies": {
|
||||
"serve": "^13.0.2",
|
||||
"socket.io": "^4.3.1"
|
||||
},
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux",
|
||||
"win32"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^21.0.1",
|
||||
"@rollup/plugin-node-resolve": "^13.0.6",
|
||||
"crypto-js": "^4.1.1",
|
||||
"eslint": "^8.1.0",
|
||||
"rollup": "^2.59.0",
|
||||
"uglify-js": "^3.14.3"
|
||||
}
|
||||
}
|
||||
|
|
11
pm2.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"name": "cryptalk",
|
||||
"script": "server/server.js",
|
||||
"instances": "1",
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"env_production" : {
|
||||
"NODE_ENV": "production"
|
||||
}
|
||||
}
|
40
public/js/bootstrap.js
vendored
|
@ -1,40 +0,0 @@
|
|||
// This Javascript file is the only file besides fandango.js that will be fetched through DOM.
|
||||
|
||||
// Setup fandango
|
||||
fandango.defaults({
|
||||
baseUrl: 'js/cryptalk_modules/',
|
||||
paths: {
|
||||
websocket: 'https://cdnjs.cloudflare.com/ajax/libs/socket.io/0.9.16/socket.io.min.js',
|
||||
// Newer version:
|
||||
// We'll have to fix the Access Control issue first though (https://github.com/Automattic/socket.io-client/issues/641).
|
||||
// websocket: 'https://cdn.socket.io/socket.io-1.1.0.js',
|
||||
aes: 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js',
|
||||
SHA1: 'https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/sha1.js',
|
||||
domReady: 'https://cdnjs.cloudflare.com/ajax/libs/require-domReady/2.0.1/domReady.min.js'
|
||||
},
|
||||
|
||||
// CryptoJs AES does not support AMD modules. We'll have to create a shim.
|
||||
shim: {
|
||||
aes: {
|
||||
exports: function () { // String (the global variable name) or a function; returning the desired variable.
|
||||
return CryptoJS.AES;
|
||||
}
|
||||
},
|
||||
SHA1: {
|
||||
exports: function () {
|
||||
return CryptoJS.SHA1;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Require main cryptalk module.
|
||||
require(['cryptalk'], function () {}, function (e) {
|
||||
document.getElementById('chat').innerHTML = '<li><i class="fatal">Fatal: An error was thrown during initialization causing the application to stop.<br>Examine the logs for more details.</i></li>';
|
||||
|
||||
if (console.log) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
throw e;
|
||||
});
|
|
@ -1,179 +0,0 @@
|
|||
define(['fandango', 'websocket', 'aes', 'SHA1'], function (fandango, websocket, aes, SHA1) {
|
||||
var exports = {
|
||||
selector: 0,
|
||||
utilities: {},
|
||||
prototype: {}
|
||||
},
|
||||
|
||||
// Shortcuts
|
||||
utils = exports.utilities,
|
||||
proto = exports.prototype,
|
||||
|
||||
each = fandango.each,
|
||||
|
||||
/**
|
||||
* Regex for matching NaN.
|
||||
*
|
||||
* @property reNaN
|
||||
* @type {Regex}
|
||||
* @private
|
||||
*/
|
||||
reDigits = /^\d+$/;
|
||||
|
||||
// The DOM selector engine
|
||||
exports.selector = function (selector) {
|
||||
var match,
|
||||
matches = [];
|
||||
|
||||
if (selector === document) {
|
||||
matches.push(document);
|
||||
} else {
|
||||
selector = selector.slice(1);
|
||||
|
||||
if ((match = document.getElementById(selector))) {
|
||||
matches.push(match);
|
||||
}
|
||||
}
|
||||
|
||||
// Must ALWAYS return a native array: []
|
||||
return matches;
|
||||
};
|
||||
|
||||
// Namespace SHA1
|
||||
utils.SHA1 = function (string) {
|
||||
return SHA1(string).toString();
|
||||
};
|
||||
|
||||
// Namespace encode
|
||||
utils.AES = {
|
||||
decrypt: function (string, fgh) {
|
||||
return aes.decrypt(string, fgh).toString(CryptoJS.enc.Utf8);
|
||||
},
|
||||
|
||||
encrypt: function (string, fgh) {
|
||||
return aes.encrypt(string, fgh).toString();
|
||||
}
|
||||
};
|
||||
|
||||
// Namespace websocket
|
||||
utils.Websocket = {
|
||||
connect: websocket.connect,
|
||||
on: websocket.on
|
||||
};
|
||||
|
||||
utils.ssplit = function (string, seperator) {
|
||||
var components = string.split(seperator);
|
||||
return [components.shift(), components.join(seperator)];
|
||||
};
|
||||
|
||||
utils.activeElement = function () {
|
||||
try { return document.activeElement; } catch (e) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes all characters but 0 - 9 from given string.
|
||||
*
|
||||
* @method digits
|
||||
* @param {String} str The string to sanitize
|
||||
* @return {String} The sanitized string
|
||||
* @example
|
||||
* $.digits('foo8bar'); // `8`
|
||||
* $.digits('->#5*duckM4N!!!111'); // `54111`
|
||||
*/
|
||||
utils.isDigits = function(value) {
|
||||
return reDigits.test(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* A very simple templating function.
|
||||
* @param {} str [description]
|
||||
* @param {[type]} map [description]
|
||||
* @return {[type]} [description]
|
||||
*/
|
||||
utils.template = function (str, map) {
|
||||
return str && str.replace(/{(\w+)}/gi, function(outer, inner) {
|
||||
return map.hasOwnProperty(inner) ? map[inner] : outer /* '' */;
|
||||
});
|
||||
};
|
||||
|
||||
utils.getJSON = function (path, onSuccess, onError) {
|
||||
var data, request = new XMLHttpRequest();
|
||||
request.open('GET', path, true);
|
||||
|
||||
request.onreadystatechange = function() {
|
||||
if (this.readyState === 4) {
|
||||
if (this.status >= 200 && this.status < 400) {
|
||||
try {
|
||||
onSuccess && onSuccess(JSON.parse(this.responseText));
|
||||
} catch (e) {
|
||||
onError && onError();
|
||||
}
|
||||
} else {
|
||||
onError && onError();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
request.send();
|
||||
request = null;
|
||||
};
|
||||
|
||||
// Part of this is originating from mustasche.js
|
||||
// Code: https://github.com/janl/mustache.js/blob/master/mustache.js#L43
|
||||
// License: https://github.com/janl/mustache.js/blob/master/LICENSE
|
||||
utils.escapeHtml = (function () {
|
||||
var pattern = /[&<>"'\/]/g,
|
||||
entities = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": ''',
|
||||
'/': '/'
|
||||
};
|
||||
|
||||
return function (string) {
|
||||
return String(string).replace(pattern, function (s) {
|
||||
return entities[s];
|
||||
});
|
||||
};
|
||||
}());
|
||||
|
||||
// Extremely naive implementations of .html() and .append()
|
||||
proto.html = function (string) {
|
||||
each(this, function (element) {
|
||||
element.innerHTML = string;
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
proto.append = function (string) {
|
||||
for (var i = 0, len = this.length; i < len; i++) {
|
||||
this[0].innerHTML += string;
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Naive implementations of .on()
|
||||
proto.on = function (eventName, callback) {
|
||||
for (var i = 0, len = this.length; i < len; i++) {
|
||||
if (this[0].addEventListener) {
|
||||
this[0].addEventListener(eventName, callback, false);
|
||||
} else if (this[0].attachEvent) {
|
||||
this[0].attachEvent('on' + eventName, callback);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
proto.focus = function () {
|
||||
this[0].focus();
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
return exports;
|
||||
})
|
|
@ -1,69 +0,0 @@
|
|||
/* Usage:
|
||||
|
||||
mediator.emit('audio:play',message);
|
||||
mediator.emit('audio:on');
|
||||
mediator.emit('audio:off');
|
||||
|
||||
*/
|
||||
|
||||
// Sounds module, used for emitting those annoying bl-up sounds when receiving a message
|
||||
define(['queue','mediator'], function (queue,mediator) {
|
||||
|
||||
var ac = false,
|
||||
enabled = true,
|
||||
|
||||
// Recursive function for playing tones, takes an array of [tone,start_ms,duration_ms] - entries
|
||||
// i is only used for recursion
|
||||
playTones = function (tones, i) {
|
||||
|
||||
// Parameter defaults
|
||||
i = (i === undefined) ? 0 : i;
|
||||
|
||||
// Stop if we've reached the end of iteration, and require ac
|
||||
if (!ac || !enabled || !(i < Object.keys(tones).length)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add tones to execution queue
|
||||
var current_tones = tones[i],
|
||||
freqs = current_tones[0],
|
||||
start = current_tones[1],
|
||||
duration = current_tones[2];
|
||||
|
||||
var o = ac.createOscillator();
|
||||
var g = ac.createGain();
|
||||
o.frequency.value = freqs;
|
||||
o.connect(g);
|
||||
g.gain.value = 0.25;
|
||||
g.connect(ac.destination);
|
||||
queue.add_function_delayed(start,function() { o.noteOn(0); });
|
||||
queue.add_function_delayed(start+duration,function() { o.noteOff(0); });
|
||||
|
||||
// Iterate, or start playing
|
||||
i++;
|
||||
if( i < Object.keys(tones).length ) {
|
||||
playTones(tones,i);
|
||||
} else {
|
||||
queue.run();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
on = function() {
|
||||
enabled = true;
|
||||
},
|
||||
|
||||
off = function() {
|
||||
enabled = false;
|
||||
};
|
||||
|
||||
if (window.AudioContext || window.webkitAudioContext) {
|
||||
ac = new (window.AudioContext || window.webkitAudioContext);
|
||||
}
|
||||
|
||||
mediator.on('audio:play', function (message) { playTones(message); });
|
||||
mediator.on('audio:on', function (message) { on(); });
|
||||
mediator.on('audio:off', function (message) { off(); });
|
||||
|
||||
});
|
|
@ -1,552 +0,0 @@
|
|||
// Main cryptalk module
|
||||
define({
|
||||
compiles: ['$'],
|
||||
requires: ['mediator', 'hosts', 'templates', 'audio', 'fandango','notifications', 'sounds', 'win']
|
||||
}, function ($, requires, data) {
|
||||
var socket,
|
||||
key,
|
||||
host,
|
||||
room,
|
||||
hash,
|
||||
nick,
|
||||
mute = false,
|
||||
|
||||
settings = {},
|
||||
|
||||
history = [],
|
||||
history_pos = -1,
|
||||
history_keep = 4,
|
||||
history_timer,
|
||||
|
||||
// Collection of DOM components
|
||||
components = {
|
||||
chat: $('#chat'),
|
||||
input: $('#input'),
|
||||
inputWrapper: $('#input_wrapper')
|
||||
},
|
||||
|
||||
// Shortcut
|
||||
hosts = requires.hosts,
|
||||
fandango = requires.fandango,
|
||||
mediator = requires.mediator,
|
||||
templates = requires.templates,
|
||||
sounds = requires.sounds,
|
||||
win = requires.win,
|
||||
|
||||
lockInput = function () {
|
||||
components.input[0].setAttribute('disabled', 'disabled');
|
||||
components.inputWrapper[0].className = 'loading';
|
||||
},
|
||||
|
||||
unlockInput = function () {
|
||||
components.input[0].removeAttribute('disabled');
|
||||
components.inputWrapper[0].className = '';
|
||||
components.input.focus();
|
||||
},
|
||||
|
||||
showNotification = function (type, nick, text) {
|
||||
|
||||
var title = (type!='message') ? 'Cryptalk' : nick,
|
||||
icon = (type == 'message') ? 'gfx/icon_128x128.png' : (type == 'error') ? 'gfx/icon_128x128_error.png' : 'gfx/icon_128x128_info.png';
|
||||
|
||||
// Emit notification
|
||||
mediator.emit('notification:send',
|
||||
{
|
||||
title: title.substring(0, 20),
|
||||
body: text.substring(0, 80),
|
||||
icon: icon
|
||||
});
|
||||
|
||||
// Emit sound
|
||||
if ( type == 'message' ) mediator.emit('audio:play',sounds.message);
|
||||
|
||||
},
|
||||
|
||||
// Adds a new message to the DOM
|
||||
post = function (type, text, clearChat, clearBuffer, nick) {
|
||||
|
||||
var tpl = templates.post[type],
|
||||
post,
|
||||
data = fandango.merge({}, settings, {
|
||||
nick: nick,
|
||||
room: room
|
||||
});
|
||||
|
||||
data.text = $.template(text, data);
|
||||
post = $.template(tpl, data);
|
||||
|
||||
// Always clear the input after a post
|
||||
if (clearBuffer) {
|
||||
clearInput();
|
||||
}
|
||||
|
||||
showNotification(type, nick, text);
|
||||
|
||||
// Append the post to the chat DOM element
|
||||
components.chat[clearChat ? 'html' : 'append'](post);
|
||||
},
|
||||
|
||||
// Chat related commands
|
||||
commands = {
|
||||
help: function (payload, done) {
|
||||
post('motd', templates.help);
|
||||
done();
|
||||
},
|
||||
|
||||
host: function () {
|
||||
post('info', JSON.stringify(host || {}));
|
||||
},
|
||||
|
||||
hosts: function (force, done) {
|
||||
var i = 0,
|
||||
left = hosts.hosts.length,
|
||||
host,
|
||||
strhosts = '\n',
|
||||
callback = function (host, index, isUp) {
|
||||
return function (hostSettings) {
|
||||
host.settings = (isUp ? hostSettings : 0);
|
||||
|
||||
strhosts += $.template(templates.messages[(isUp ? 'host_available' : 'host_unavailable')], {
|
||||
name: host.name,
|
||||
path: host.path,
|
||||
index: index
|
||||
});
|
||||
|
||||
if (--left === 0) {
|
||||
post('info', strhosts);
|
||||
done();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
//
|
||||
force = (force && force.toLowerCase() === 'force');
|
||||
|
||||
// Loop through all the hosts
|
||||
while ((host = hosts.hosts[i])) {
|
||||
if (!force && host.settings !== undefined) {
|
||||
if (host.settings) {
|
||||
callback(host, i, 1)();
|
||||
} else {
|
||||
callback(host, i, 0)();
|
||||
}
|
||||
} else {
|
||||
require([host.path], callback(host, i, 1), callback(host, i, 0));
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
},
|
||||
|
||||
connect: function (toHost, done) {
|
||||
var request;
|
||||
|
||||
if (host && host.connected) {
|
||||
done();
|
||||
post('error', $.template(templates.messages.already_connected, {
|
||||
host: host.name || 'localhost'
|
||||
}));
|
||||
return done();
|
||||
}
|
||||
|
||||
if ($.isDigits(toHost)) {
|
||||
if ((host = hosts.hosts[+toHost])) {
|
||||
if (host.settings) {
|
||||
settings = host.settings;
|
||||
} else {
|
||||
request = host.path;
|
||||
}
|
||||
} else {
|
||||
post('error', 'Undefined host index: ' + toHost);
|
||||
return done();
|
||||
}
|
||||
} else if (fandango.is(toHost, 'untyped')) {
|
||||
settings = toHost.settings;
|
||||
} else { // Assume string
|
||||
request = toHost;
|
||||
}
|
||||
|
||||
if (request) {
|
||||
return require([request], function (settings) {
|
||||
host.settings = settings;
|
||||
commands.connect(toHost, done);
|
||||
}, function () {
|
||||
post('error', 'Could not fetch host settings: ' + request);
|
||||
return done();
|
||||
});
|
||||
}
|
||||
|
||||
// Push 'Connecting...' message
|
||||
post('info', $.template(templates.messages.connecting, {
|
||||
host: host.name || 'localhost'
|
||||
}));
|
||||
|
||||
// The one and only socket
|
||||
socket = $.Websocket.connect(host.host, {
|
||||
forceNew: true,
|
||||
'force new connection': true
|
||||
});
|
||||
|
||||
// Bind socket events
|
||||
socket
|
||||
.on('room:generated', function (data) {
|
||||
var sanitized = $.escapeHtml(data);
|
||||
post('server', $.template(templates.server.room_generated, { payload: sanitized }));
|
||||
socket.emit('room:join', sanitized);
|
||||
})
|
||||
|
||||
.on('room:joined', function (data) {
|
||||
room = $.escapeHtml(data);
|
||||
post('info', $.template(templates.messages.joined_room, { roomName: room }));
|
||||
|
||||
// Automatically count persons on join
|
||||
socket.emit('room:count');
|
||||
})
|
||||
|
||||
.on('room:left', function () {
|
||||
post('info', $.template(templates.messages.left_room, { roomName: room }));
|
||||
|
||||
// Clear history on leaving room
|
||||
clearHistory();
|
||||
|
||||
room = false;
|
||||
})
|
||||
|
||||
.on('message:send', function (data) {
|
||||
var decrypted = $.AES.decrypt(data.msg, room + key),
|
||||
sanitized = $.escapeHtml(decrypted),
|
||||
nick = !data.nick ? templates.default_nick : $.escapeHtml($.AES.decrypt(data.nick, room + key));
|
||||
|
||||
if (!decrypted) {
|
||||
post('error', templates.messages.unable_to_decrypt);
|
||||
} else {
|
||||
post('message', sanitized, false, false, nick);
|
||||
}
|
||||
})
|
||||
|
||||
.on('message:server', function (data) {
|
||||
if( data.msg ) {
|
||||
var sanitized = $.escapeHtml(data.msg);
|
||||
if( templates.server[sanitized] ) {
|
||||
if( data.payload !== undefined ) {
|
||||
var sanitized_payload = $.escapeHtml(data.payload);
|
||||
post('server', $.template(templates.server[sanitized], { payload: sanitized_payload }));
|
||||
} else {
|
||||
post('server', templates.server[sanitized]);
|
||||
}
|
||||
} else {
|
||||
post('error', templates.server.bogus);
|
||||
}
|
||||
} else {
|
||||
post('error', templates.server.bogus);
|
||||
}
|
||||
})
|
||||
|
||||
.on('connect', function () {
|
||||
// Tell the user that the chat is ready to interact with
|
||||
post('info', $.template(templates.messages.connected, {
|
||||
host: host.name || 'localhost'
|
||||
}));
|
||||
|
||||
host.connected = 1;
|
||||
|
||||
done();
|
||||
})
|
||||
|
||||
.on('disconnect', function () {
|
||||
room = 0;
|
||||
key = 0;
|
||||
host.connected = 0;
|
||||
|
||||
// Tell the user that the chat is ready to interact with
|
||||
post('info', $.template(templates.messages.disconnected, {
|
||||
host: host.name || 'localhost'
|
||||
}));
|
||||
})
|
||||
|
||||
.on('error', function () {
|
||||
room = 0;
|
||||
key = 0;
|
||||
host.connected = 0;
|
||||
post('error', templates.messages.socket_error);
|
||||
done();
|
||||
});
|
||||
},
|
||||
|
||||
reconnect: function (foo, done) {
|
||||
if (host) {
|
||||
if (host.connected) {
|
||||
commands.disconnect();
|
||||
commands.connect(host, done);
|
||||
} else {
|
||||
commands.connect(host, done);
|
||||
}
|
||||
} else {
|
||||
done();
|
||||
return post('error', templates.messages.reconnect_no_host);
|
||||
}
|
||||
},
|
||||
|
||||
disconnect: function () {
|
||||
socket.disconnect();
|
||||
},
|
||||
|
||||
clear: function () {
|
||||
components.chat.html('');
|
||||
|
||||
// Clear command history on clearing buffer
|
||||
clearHistory();
|
||||
},
|
||||
|
||||
leave: function () {
|
||||
if (room) {
|
||||
socket.emit('room:leave', room);
|
||||
} else {
|
||||
post('error', templates.messages.leave_from_nowhere);
|
||||
}
|
||||
},
|
||||
|
||||
count: function () {
|
||||
if (room) {
|
||||
socket.emit('room:count');
|
||||
} else {
|
||||
post('error', templates.messages.not_in_room);
|
||||
}
|
||||
},
|
||||
|
||||
key: function (payload) {
|
||||
if (!host) {
|
||||
return post('error', templates.messages.key_no_host);
|
||||
}
|
||||
|
||||
// Make sure the key meets the length requirements
|
||||
if (payload.length > settings.key_maxLen) {
|
||||
return post('error', templates.messages.key_to_long);
|
||||
} else if (payload.length < settings.key_minLen) {
|
||||
return post('error', templates.messages.key_to_short);
|
||||
}
|
||||
|
||||
// Set key
|
||||
key = payload;
|
||||
|
||||
// Inform that the key has been set
|
||||
post('info', (room ? templates.messages.key_ok_ready : templates.messages.key_ok_but_no_room));
|
||||
},
|
||||
|
||||
nick: function (payload) {
|
||||
// Make sure the key meets the length requirements
|
||||
if (payload.length > settings.nick_maxLen) {
|
||||
return post('error', templates.messages.nick_to_long);
|
||||
} else if (payload.length < settings.nick_minLen) {
|
||||
return post('error', templates.messages.nick_to_short);
|
||||
}
|
||||
|
||||
// Set nick
|
||||
nick = payload;
|
||||
|
||||
// Inform that the key has been set
|
||||
post('info', $.template(templates.messages.nick_set, { nick: $.escapeHtml(nick)}));
|
||||
},
|
||||
|
||||
mute: function () {
|
||||
mute = true;
|
||||
return post('info', templates.messages.muted);
|
||||
},
|
||||
|
||||
unmute: function () {
|
||||
mute = false;
|
||||
return post('info', templates.messages.unmuted);
|
||||
},
|
||||
|
||||
title: function (payload) {
|
||||
win.setTitle(payload);
|
||||
return post('info', $.template(templates.messages.title_set, { title: $.escapeHtml(payload)}));
|
||||
},
|
||||
|
||||
join: function (payload) {
|
||||
if (!host) {
|
||||
return post('error', templates.messages.join_no_host);
|
||||
}
|
||||
|
||||
return (
|
||||
room
|
||||
? post('error', templates.messages.already_in_room)
|
||||
: socket.emit('room:join', $.SHA1(payload))
|
||||
);
|
||||
},
|
||||
|
||||
generate: function (payload) {
|
||||
return (
|
||||
room
|
||||
? post('error', templates.messages.already_in_room)
|
||||
: socket.emit('room:generate')
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
// Push input buffer to history
|
||||
pushHistory = function (b) {
|
||||
history.push(b);
|
||||
|
||||
// Shift oldest buffer if we have more than we should keep
|
||||
if (history.length > history_keep) {
|
||||
history.shift();
|
||||
}
|
||||
},
|
||||
|
||||
// Clear input buffer history
|
||||
clearHistory = function() {
|
||||
history = [];
|
||||
history_pos = -1;
|
||||
},
|
||||
|
||||
// Clear input buffer
|
||||
clearInput = function() {
|
||||
fandango.subordinate(function () {
|
||||
components.input[0].value = '';
|
||||
});
|
||||
},
|
||||
|
||||
// Handler for the document`s keyDown-event.
|
||||
onKeyDown = function (e) {
|
||||
var buffer,
|
||||
parts,
|
||||
payload,
|
||||
command,
|
||||
save;
|
||||
|
||||
// The Document object is bound to this element.
|
||||
// If the active element is not the input, focus on it and exit the function.
|
||||
// Ignore this when ctrl and/or alt is pressed!
|
||||
if (!e.ctrlKey && !e.altKey && components.input[0] !== $.activeElement()) {
|
||||
return components.input.focus();
|
||||
}
|
||||
|
||||
// Reset command history clear timer
|
||||
clearTimeout(history_timer);
|
||||
history_timer = setTimeout(clearHistory, 60000);
|
||||
|
||||
// Check for escape key, this does nothing but clear the input buffer and reset history position
|
||||
if (e.keyCode == 27) {
|
||||
history_pos = -1;
|
||||
clearInput();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for up or down-keys, they handle the history position
|
||||
if (e.keyCode == 38 || e.keyCode == 40) {
|
||||
|
||||
if (e.keyCode == 38 ) { history_pos = (history_pos > history.length - 2) ? -1 : history_pos = history_pos + 1; }
|
||||
else { history_pos = (history_pos <= 0) ? -1 : history_pos = history_pos - 1; }
|
||||
|
||||
var input = components.input[0];
|
||||
input.value = (history_pos == -1) ? '' : history[history.length-1-history_pos];
|
||||
|
||||
// Wierd hack to move caret to end of input-box
|
||||
setTimeout(function() {if(input.setSelectionRange) input.setSelectionRange(input.value.length, input.value.length);}, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Return immediatly if the buffer is empty or if the hit key was not <enter>
|
||||
if (e.keyCode !== 13 || !(buffer = components.input[0].value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset current history position to 0 (last command)
|
||||
history_pos = -1;
|
||||
|
||||
// Handle command
|
||||
if ((buffer[0] || buffer.slice(0, 1)) === '/') {
|
||||
parts = $.ssplit(buffer.slice(1), ' ');
|
||||
command = parts[0];
|
||||
payload = parts[1];
|
||||
|
||||
// Check that there is an handler for this command
|
||||
if (!commands[command]) {
|
||||
pushHistory(buffer);
|
||||
return post('error', $.template(templates.messages.unrecognized_command, { commandName: command }));
|
||||
}
|
||||
|
||||
// Some commands are asynchrounous;
|
||||
// If the command expects more than one argument, the second argument is a callback that is called when the command is done.
|
||||
if (commands[command].length > 1) {
|
||||
// Lock the input from further interaction
|
||||
lockInput();
|
||||
|
||||
// Execute command handler with callback function.
|
||||
commands[command](payload, unlockInput);
|
||||
} else {
|
||||
// Execute normally.
|
||||
commands[command](payload);
|
||||
}
|
||||
|
||||
// Clear input field
|
||||
clearInput();
|
||||
|
||||
// Save to history
|
||||
if(command !== 'key') {
|
||||
pushHistory(buffer);
|
||||
}
|
||||
|
||||
} else /* Handle ordinary message */ {
|
||||
|
||||
if (!room || !key) {
|
||||
// Push buffer to history and clear input field
|
||||
pushHistory(buffer); clearInput();
|
||||
|
||||
// Make sure that the user has joined a room and the key is set
|
||||
return (!room) ? post('error', templates.messages.msg_no_room) : post('error', templates.messages.msg_no_key);
|
||||
}
|
||||
|
||||
// Before sending the message.
|
||||
// Encrypt message using room UUID as salt and key as pepper.
|
||||
socket.emit('message:send', {
|
||||
room: room,
|
||||
msg: $.AES.encrypt(buffer, room + key).toString(),
|
||||
nick: nick ? $.AES.encrypt(nick, room + key).toString() : false
|
||||
});
|
||||
|
||||
// And clear the the buffer
|
||||
clearInput();
|
||||
|
||||
// Save to history
|
||||
pushHistory(buffer);
|
||||
}
|
||||
};
|
||||
|
||||
// Bind the necessary DOM events
|
||||
$(document).on('keydown', onKeyDown);
|
||||
|
||||
// Put focus on the message input
|
||||
components.input.focus();
|
||||
|
||||
// Post the help/welcome message
|
||||
post('motd', templates.motd, true);
|
||||
|
||||
// Route mediator messages
|
||||
mediator.on('window:focused',function() {
|
||||
mediator.emit('audio:off');
|
||||
mediator.emit('notification:off');
|
||||
});
|
||||
mediator.on('window:blurred',function() {
|
||||
if( !mute ) mediator.emit('audio:on');
|
||||
mediator.emit('notification:on');
|
||||
});
|
||||
|
||||
unlockInput();
|
||||
|
||||
// It's possible to provide room and key using the hashtag.
|
||||
// The room and key is then seperated by semicolon (room:key).
|
||||
// If there is no semicolon present, the complete hash will be treated as the room name and the key has to be set manually.
|
||||
commands.connect(hosts.autoconnect, function() {
|
||||
if (host && (hash = window.location.hash)) {
|
||||
parts = hash.slice(1).split(':');
|
||||
|
||||
parts[0] && commands.join(parts[0]);
|
||||
parts[1] && commands.key(parts[1]);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
|
@ -1,20 +0,0 @@
|
|||
define({
|
||||
// Used to autoconnect to specific host.
|
||||
// Points to a specific index in the 'hosts' array.
|
||||
// Use -1 to not autoconnect.
|
||||
autoconnect: 0,
|
||||
|
||||
// A collection of hosts to choose from
|
||||
hosts: [
|
||||
{
|
||||
name: 'default',
|
||||
host: '',
|
||||
path: '/js/cryptalk_modules/settings.js'
|
||||
}/*,
|
||||
{
|
||||
name: 'Example',
|
||||
host: 'http://www.example.com',
|
||||
path: 'http://www.example.com/js/cryptalk_modules/settings.js'
|
||||
}*/
|
||||
]
|
||||
});
|
|
@ -1,188 +0,0 @@
|
|||
(function (self, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define([], factory);
|
||||
} else if (typeof exports === 'object') { // Node
|
||||
module.exports = factory();
|
||||
} else {
|
||||
// Attaches to the current context
|
||||
self.castrato = factory;
|
||||
}
|
||||
}(this, (function () {
|
||||
var
|
||||
/**
|
||||
* Contains the next unique node id.
|
||||
*
|
||||
* @property index
|
||||
* @type {Integer}
|
||||
* @private
|
||||
*/
|
||||
index = 0,
|
||||
|
||||
/**
|
||||
* Contains all subscriptions
|
||||
*
|
||||
* @property subs
|
||||
* @type {Object}
|
||||
* @private
|
||||
*/
|
||||
subs = {},
|
||||
|
||||
/**
|
||||
* An empty function.
|
||||
* This method does not accept any arguments.
|
||||
*
|
||||
* @property noop
|
||||
* @type {function}
|
||||
* @private
|
||||
*/
|
||||
noop = function () {};
|
||||
|
||||
/**
|
||||
* Creates a new entry in the `subs` object.
|
||||
*
|
||||
* @method on
|
||||
* @private
|
||||
* @param {Integer} fromId The unique subscriber ID.
|
||||
* @param {String} event The event to subscribe to.
|
||||
* @param {Function} func A function to execute when the event is triggered.
|
||||
*/
|
||||
function on (fromId, event, func) {
|
||||
(subs[event] || (subs[event] = [])).push([fromId, func, func.length]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all event handlers originating from `fromId` and optionally filter by handler.
|
||||
*
|
||||
* @method off
|
||||
* @private
|
||||
* @param {Integer} fromId The unique subscriber ID.
|
||||
* @param {String} event The event to unsubscribe from.
|
||||
* @param {Function} [func=null] The original handler that was attached to this event. If not passed, all subscriptions will be removed.
|
||||
*/
|
||||
function off (fromId, event, func) {
|
||||
var sub,
|
||||
i = 0,
|
||||
toSubs = subs[event];
|
||||
|
||||
if (toSubs) {
|
||||
while ((sub = toSubs[i++])) {
|
||||
if (sub[0] === fromId && (!func || func === sub[1])) {
|
||||
toSubs.splice(--i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through all subscriptions, calling all handlers attached to given `event`.
|
||||
*
|
||||
* @method emit
|
||||
* @private
|
||||
* @param {Integer} fromId The unique subscriber ID.
|
||||
* @param {String} event The event to emit
|
||||
* @param {Object} [data=undefined] Parameters to pass along to the event handler.
|
||||
* @param {Function} [func=undefined] A function to execute when all event handlers has returned.
|
||||
*/
|
||||
function emit (persistent, event, data, func) {
|
||||
var sub,
|
||||
toSubs = subs[event] || [],
|
||||
total = toSubs.length,
|
||||
left = total,
|
||||
loop = total,
|
||||
answers = [],
|
||||
done;
|
||||
|
||||
if (loop) {
|
||||
done = !func ? noop : function (data) {
|
||||
if (data) {
|
||||
answers.push(data);
|
||||
}
|
||||
|
||||
if (!--left) {
|
||||
func(answers, total);
|
||||
func = 0;
|
||||
}
|
||||
};
|
||||
|
||||
while ((sub = toSubs[--loop])) {
|
||||
sub[1](data, (sub[2] > 1) ? done : left--);
|
||||
}
|
||||
}
|
||||
|
||||
// `func` get destructed when called.
|
||||
// It has to be called at least once - even if no one was subscribing.
|
||||
// Execute it if it still exists.
|
||||
if (func) {
|
||||
func(answers, total);
|
||||
}
|
||||
}
|
||||
|
||||
return function () {
|
||||
var nodeId = index++;
|
||||
|
||||
return {
|
||||
/**
|
||||
* Execute all handlers attached to the given event.
|
||||
*
|
||||
* @method emit
|
||||
* @param {String} event The event to emit
|
||||
* @param {Object} [data=undefined] Parameters to pass along to the event handler.
|
||||
* @param {Function} [func=undefined] A function to execute when all event handlers has returned.
|
||||
* @return {Object} `this`
|
||||
* @example
|
||||
* $.emit('something');
|
||||
* $.emit('something', { foo: 'bar' });
|
||||
* $.emit('something', { foo: 'bar' }, function (data, subscribers) {
|
||||
* console.log('Emit done, a total of ' + subscribers + ' subscribers returned: ', data);
|
||||
* });
|
||||
*/
|
||||
emit: function (persistent, event, data, func) {
|
||||
// emit('something', { data: true }, function () {});
|
||||
if (persistent !== true || persistent !== false) {
|
||||
func = data;
|
||||
data = event;
|
||||
event = persistent;
|
||||
persistent = false;
|
||||
}
|
||||
|
||||
emit(persistent, event, data, func);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach an event handler function for one event.
|
||||
*
|
||||
* @method on
|
||||
* @param {String} event The event to subscribe to.
|
||||
* @param {Function} func A function to execute when the event is triggered.
|
||||
* @return {Object} `this`
|
||||
* @example
|
||||
* $.on('something', function (data) {
|
||||
* console.log('Got something!', data);
|
||||
* });
|
||||
*/
|
||||
on: function (event, func) {
|
||||
on(nodeId, event, func);
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes an event handler function for one event.
|
||||
*
|
||||
* @method off
|
||||
* @param {String} event The event to unsubscribe from.
|
||||
* @param {Function} [func=null] The original handler that was attached to this event. If not passed, all subscriptions will be removed.
|
||||
* @return {Object} `this`
|
||||
* @example
|
||||
* $.off('something');
|
||||
* $.off('something else', handler);
|
||||
*/
|
||||
off: function (event) {
|
||||
off(nodeId, event);
|
||||
return this;
|
||||
}
|
||||
};
|
||||
};
|
||||
}())));
|
|
@ -1,44 +0,0 @@
|
|||
define(function (){
|
||||
var exports = {},
|
||||
queue = [],
|
||||
now = function () {
|
||||
return performance.now() || Date.now();
|
||||
};
|
||||
|
||||
exports.add_function_delayed = function(delay, callback, data) {
|
||||
queue.push({
|
||||
func: callback,
|
||||
pushed: now(),
|
||||
delay: delay,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
exports.get = function () {
|
||||
return queue;
|
||||
}
|
||||
|
||||
exports.run = function () {
|
||||
var i = 0,
|
||||
current,
|
||||
lrt_inner;
|
||||
|
||||
while (current = queue[i++]) {
|
||||
if (now() - current.pushed > current.delay) {
|
||||
current.func();
|
||||
queue.splice(i - 1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (queue.length) {
|
||||
// Waste a ms to prevent callstack overflow
|
||||
lrt_inner = now();
|
||||
|
||||
while (now() - lrt_inner < 1) { void 0; };
|
||||
|
||||
exports.run();
|
||||
}
|
||||
}
|
||||
|
||||
return exports;
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
define({
|
||||
nick_maxLen: 20,
|
||||
nick_minLen: 3,
|
||||
|
||||
key_maxLen: Infinity,
|
||||
key_minLen: 8
|
||||
});
|
10
public/js/vendor/aes.v3.1.2.min.js
vendored
|
@ -1,10 +0,0 @@
|
|||
/*
|
||||
CryptoJS v3.1.2
|
||||
code.google.com/p/crypto-js
|
||||
(c) 2009-2013 by Jeff Mott. All rights reserved.
|
||||
code.google.com/p/crypto-js/wiki/License
|
||||
*/
|
||||
(function(){for(var q=CryptoJS,x=q.lib.BlockCipher,r=q.algo,j=[],y=[],z=[],A=[],B=[],C=[],s=[],u=[],v=[],w=[],g=[],k=0;256>k;k++)g[k]=128>k?k<<1:k<<1^283;for(var n=0,l=0,k=0;256>k;k++){var f=l^l<<1^l<<2^l<<3^l<<4,f=f>>>8^f&255^99;j[n]=f;y[f]=n;var t=g[n],D=g[t],E=g[D],b=257*g[f]^16843008*f;z[n]=b<<24|b>>>8;A[n]=b<<16|b>>>16;B[n]=b<<8|b>>>24;C[n]=b;b=16843009*E^65537*D^257*t^16843008*n;s[f]=b<<24|b>>>8;u[f]=b<<16|b>>>16;v[f]=b<<8|b>>>24;w[f]=b;n?(n=t^g[g[g[E^t]]],l^=g[g[l]]):n=l=1}var F=[0,1,2,4,8,
|
||||
16,32,64,128,27,54],r=r.AES=x.extend({_doReset:function(){for(var c=this._key,e=c.words,a=c.sigBytes/4,c=4*((this._nRounds=a+6)+1),b=this._keySchedule=[],h=0;h<c;h++)if(h<a)b[h]=e[h];else{var d=b[h-1];h%a?6<a&&4==h%a&&(d=j[d>>>24]<<24|j[d>>>16&255]<<16|j[d>>>8&255]<<8|j[d&255]):(d=d<<8|d>>>24,d=j[d>>>24]<<24|j[d>>>16&255]<<16|j[d>>>8&255]<<8|j[d&255],d^=F[h/a|0]<<24);b[h]=b[h-a]^d}e=this._invKeySchedule=[];for(a=0;a<c;a++)h=c-a,d=a%4?b[h]:b[h-4],e[a]=4>a||4>=h?d:s[j[d>>>24]]^u[j[d>>>16&255]]^v[j[d>>>
|
||||
8&255]]^w[j[d&255]]},encryptBlock:function(c,e){this._doCryptBlock(c,e,this._keySchedule,z,A,B,C,j)},decryptBlock:function(c,e){var a=c[e+1];c[e+1]=c[e+3];c[e+3]=a;this._doCryptBlock(c,e,this._invKeySchedule,s,u,v,w,y);a=c[e+1];c[e+1]=c[e+3];c[e+3]=a},_doCryptBlock:function(c,e,a,b,h,d,j,m){for(var n=this._nRounds,f=c[e]^a[0],g=c[e+1]^a[1],k=c[e+2]^a[2],p=c[e+3]^a[3],l=4,t=1;t<n;t++)var q=b[f>>>24]^h[g>>>16&255]^d[k>>>8&255]^j[p&255]^a[l++],r=b[g>>>24]^h[k>>>16&255]^d[p>>>8&255]^j[f&255]^a[l++],s=
|
||||
b[k>>>24]^h[p>>>16&255]^d[f>>>8&255]^j[g&255]^a[l++],p=b[p>>>24]^h[f>>>16&255]^d[g>>>8&255]^j[k&255]^a[l++],f=q,g=r,k=s;q=(m[f>>>24]<<24|m[g>>>16&255]<<16|m[k>>>8&255]<<8|m[p&255])^a[l++];r=(m[g>>>24]<<24|m[k>>>16&255]<<16|m[p>>>8&255]<<8|m[f&255])^a[l++];s=(m[k>>>24]<<24|m[p>>>16&255]<<16|m[f>>>8&255]<<8|m[g&255])^a[l++];p=(m[p>>>24]<<24|m[f>>>16&255]<<16|m[g>>>8&255]<<8|m[k&255])^a[l++];c[e]=q;c[e+1]=r;c[e+2]=s;c[e+3]=p},keySize:8});q.AES=x._createHelper(r)})();
|
26
public/js/vendor/domReady.v2.0.1.min.js
vendored
|
@ -1,26 +0,0 @@
|
|||
/*
|
||||
|
||||
MIT License
|
||||
-----------
|
||||
|
||||
Copyright (c) 2010-2011, The Dojo Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
define(function(){"use strict";function e(e){var t;for(t=0;e.length>t;t+=1)e[t](h)}function t(){var t=c;l&&t.length&&(c=[],e(t))}function i(){l||(l=!0,o&&clearInterval(o),t())}function n(e){return l?e(h):c.push(e),n}var r,s,o,a="undefined"!=typeof window&&window.document,l=!a,h=a?document:null,c=[];if(a){if(document.addEventListener)document.addEventListener("DOMContentLoaded",i,!1),window.addEventListener("load",i,!1);else if(window.attachEvent){window.attachEvent("onload",i),s=document.createElement("div");try{r=null===window.frameElement}catch(u){}s.doScroll&&r&&window.external&&(o=setInterval(function(){try{s.doScroll(),i()}catch(e){}},30))}"complete"===document.readyState&&i()}return n.version="2.0.1",n.load=function(e,t,i,r){r.isBuild?i(null):n(i)},n});
|
21
public/js/vendor/fandango.v20140924.min.js
vendored
|
@ -1,21 +0,0 @@
|
|||
(function(q){function C(e){this.name="TimeoutError";this.message=e||""}function D(e){this.name="RejectedError";this.message=e||""}function G(e,d,g,h){var c,a,b=[],f=0,m,l,p,s=function(a){return function(c,l){b[a]=n[c].exports;e.length===++f&&(!l&&h&&clearTimeout(m),d&&d(b))}};h=h||x.timeout;for(p=0;l=e[p++];)if((c=n[l])&&1!==c.state)if(2===c.state)if(a=new D('Could resolve all dependencies; dependency "'+l+'" has been rejected.'),g)g(a);else throw a;else 3===c.state&&s(p-1)(l,1);else v[l]||(v[l]=
|
||||
[]),v[l].unshift(s(p-1));h&&e.length<b.length&&(m=setTimeout(function(){for(var c,d,f=0,l=0;c=e[f++];)for(l=0;d=b[l++];)c===d&&(b.splice(--l,1),e.splice(--f,1));a=new C("Load timeout of "+h+'ms exceeded for module(s) "'+e.join('", "')+'".');if(g)g(a);else throw a;},h))}function y(){var e=!1,d,g,h,c,a,b,f,m;if(arguments[0]!==E){for(d=0;(g=arguments[d++])&&(_type=(typeof g)[0]||(typeof g).slice(0,1));)"s"===_type?a?h=1:a=g:"f"===_type?f?m?h=1:m=g:f=g:"o"===_type&&(k.is(g,"array")?(b={requires:g},e=
|
||||
!0):b?f=g:b=g),h&&(c=new TypeError("define called with unrecognized signature; `"+Array.prototype.join.call(arguments,", ")+"`."));b=b||{};a=a||b.UID;f=f||b.factory;m=m||b.onRejected}else(d=z.pop())?(a=arguments[1],b=d[0],f=d[1],m=d[2],e=d[3]):c=Error("Inconsistent naming queue");c||f||(b?(f=b,b={}):c=Error('Missing factory for module "'+a+'"'));!c&&n[a]&&(c=Error('Duplicate entry for module "'+a+'"'));if(c)if(m=m||arguments[2])m(c);else throw c;else a?(d=n[a]=b,d.UID=a,d.amdStyle=e,d.factory=f,d.state=
|
||||
1,d.requires=k.is(d.requires,"array")&&!!d.requires.length&&d.requires,d.inherits=k.is(d.inherits,"array")&&!!d.inherits.length&&d.inherits,d.compiles=k.is(d.compiles,"array")&&!!d.compiles.length&&d.compiles,d.instances||(d.instances={instance1:{}})):z.unshift([b,f,m,e])}var n={},x={deepCopy:!0,baseUrl:"",namespace:"default",timeout:1E3,paths:{},shim:{}},k={},E={a:1},v={},z=[],A=Array.prototype.push;C.prototype=Error.prototype;D.prototype=Error.prototype;k.is=function(){var e=Object.prototype.hasOwnProperty,
|
||||
d=Object.prototype.toString,g={array:Array.isArray||function(a){return"[object Array]"==d.call(a)},arraylike:function(a){if(!a||!a.length&&0!==a.length||g.window(a))return!1;var b=a.length;return 1===a.nodeType||g.array(a)||!g["function"](a)&&(0===b||"number"===typeof b&&0<b&&!k.is(a,"primitive")&&b-1 in a)},"boolean":function(a){return!0===a||!1===a},element:function(a){return!(!a||1!==a.nodeType)},finite:function(a){return isFinite(a)&&!isNaN(parseFloat(a))},integer:Number.isInteger||function(a){return"number"===
|
||||
typeof a&&g.finite(a)&&-9007199254740992<a&&9007199254740992>a&&Math.floor(a)===a},iterable:function(a){try{1 in obj}catch(b){return!1}return!0},nan:function(a){return g.number(a)&&a!=+a},number:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},object:function(a){return a===Object(a)},primitive:function(a){return!0===a||!1===a||null==a||!!{string:1,number:1}[typeof a]},string:function(a){return"string"==typeof a||a instanceof String},undefined:function(a){return void 0===a},untyped:function(a){if(!a||
|
||||
a.nodeType||"[object Object]"!==d.call(a)||g.window(a))return!1;try{if(a.constructor&&!e.call(a,"constructor")&&!e.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(b){return!1}for(var c in a);return void 0===c||e.call(a,c)},window:function(a){return null!=a&&a==a.window},empty:function(a){if(a){if(k.is(a,"array"))return 0===a.length;for(var b in a)if(e.call(a,b))return!1}return!0}},h=0,c=["Arguments","Date","Function","RegExp"];for(;4>h;h++)g[c[h].toLowerCase()]=function(a){a="[object "+
|
||||
a+"]";return function(b){return d.call(b)==a}}(c[h]);g.args=g.arguments;g.bool=g["boolean"];g.plain=g.untyped;return function(a,b){if(g["function"](b))return a===b(a);if(!g.string(b))return a===b;if((b=b.toLowerCase())&&g[b])return g[b](a);throw'Unknown type "'+b+'"';}}();k.each=function(){var e=Array.prototype.some;return function(d,g,h){var c,a;if(void 0===d)return obj;h=h||k;if(e&&d.some===e)return d.some(g,h),d;if(k.is(d,"array")||k.is(d,"arraylike")){c=0;for(a=d.length;c<a&&(void 0===d[c]||!g.call(h,
|
||||
d[c],c,d));c++);return d}for(c in d)if(d.hasOwnProperty(c)&&g.call(h,d[c],c,d))break;return d}}();k.merge=function(e){var d,g,h,c,a,b=arguments.length,f=!1;e=arguments[0];var m=1,l,p,s=0;if(!0===e||!1===e)f=e,e=arguments[m],m++;k.is(arguments[b-1],"function")&&(a=arguments[b-1],b--);if(void 0===e)e={};else if(k.is(e,"primitive"))throw new TypeError("Cannot merge primitive data types: merge("+Array.prototype.join.call(arguments,", ")+")");p=k.is(e,"array");if(m<b)for(;m<=b;)if(c=arguments[m++]){p=
|
||||
p&&k.is(c,"array");for(h in c)g=e[h],d=c[h],void 0===d||d===e?p&&s--:(f&&d&&((l=k.is(d,"array"))||k.is(d,"untyped"))&&(d=k.merge(f,g||(l?k.is(g,"array")?g:[]:k.is(g,"untyped")?g:{}),d,a)),a&&a(h,e,c)||(s&&(h=+h+s),e[h]=d));s=0}return e};k.subordinate=function(){function e(b){if(!b||b.data===c&&b.source&&(d===b.source||d.self&&d.self===b.source)){var e=a.shift();b=e[0];var e=e[1],l=e.length;l?1===l?b(e[0]):2===l?b(e[0],e[1]):3===l?b(e[0],e[1],e[2]):4===l?b(e[0],e[1],e[2],e[3]):b.apply(0,e):b()}}var d=
|
||||
window,g=Array.prototype.slice,h=d.postMessage,c="__fandango@"+Math.random().toString(36),a=[],b=function(){return h?function(){h(c,"*")}:function(){setTimeout(e)}}();h&&(d.addEventListener?d.addEventListener("message",e,!1):d.attachEvent&&d.attachEvent("onmessage",e));return function(c){b(a.push([arguments[arguments.length-1],g.call(arguments).slice(0,-1)])-1)}}();var H=function(){var e={},d=[].slice,g=function(){return[]};return function(h,c){function a(a,c){return new b(a,c)}function b(a,b){var c=
|
||||
0,d=f(a,b),e=d.length;this.context=b||document;this.selector=a;for(this.length=e;c<e;c++)this[c]=d[c];return this}var f,k,l,p;if(e[h])return e[h];for(p=c.length;k=c[--p];){k.selector&&(f=k.selector);if(source=k.prototype)if("function"===typeof source)source.call(b.prototype);else if(b.prototype.prototype)for(l in source)source.hasOwnProperty(l)&&(b.prototype[l]=source[l]);else b.prototype=source;if(source=k.utilities)if("function"===typeof source)source.utilities.call(a);else for(l in source)source.hasOwnProperty(l)&&
|
||||
(a[l]=source[l])}b.prototype.length=0;b.prototype.selector="";b.prototype.splice=d;b.prototype.fandango=1;f||(f=g);return e[h]=a}}(),F=function(){function e(d,e,h,c){var a,b=n[d],f=k.is(b.factory,"function"),m=f?b.factory.length:0,l=b.amdStyle,p,s;c&&(a=H(b.compiles+"",c));for(s in b.instances)c=b.instances[s],c.context=void 0===c.context?b.context:c.context,c.deepCopy=void 0===c.deepCopy?b.deepCopy:c.deepCopy,c.namespace=void 0===c.namespace?b.namespace:c.namespace,p=[],l?p=e||[]:(a&&p.push(a),e&&
|
||||
p.push(e),m>p.length&&p.push(c.data?k.merge(c.deepCopy,{},c.data,b.data):b.data),m>p.length&&p.push(k.merge(c.deepCopy,{},c,b))),c.exports=f?b.factory.apply(c.context,p)||{}:b.factory,void 0===b.exports&&(b.exports=h?c.exports?k.merge(!0,{},h,c.exports):h:c.exports);b.state=3;if(e=v[d])for(;e.length;)e.pop()(d)}return function g(h,c,a){var b,f;f=n[h];var m,l,p,s;b=[];var r;if(f){a=k.is(f.factory,"function")?f.factory.length:0;m=f.amdStyle;if(!c&&(f.requires||f.compiles||f.inherits)){f.requires&&A.apply(b,
|
||||
f.requires);f.inherits&&A.apply(b,f.inherits);f.compiles&&A.apply(b,f.compiles);for(c=0;r=b[c++];)if(n[r])if(3===n[r].state)b.splice(--c,1);else if(2===n[r].state)throw Error('Could not instantiate "'+UID+'"; dependency "'+dependency+'" has been rejected.');if(0<b.length){k.subordinate(b,function(){g(h,!0)},B);return}}void 0===(b=f.deepCopy)&&(b=x.deepCopy);f=n[h]=k.merge(b,{},x,f);if(a&&f.requires)for(p=f.requires&&(m?[]:{}),c=0;r=f.requires[c++];)m?p.push(n[r].exports):k.is(n[r].exports,"function")?
|
||||
p[r]=n[r].exports:k.merge(b,p[r]={},n[r].exports);if(f.inherits)for(s=f.inherits&&{},c=0;r=f.inherits[c++];)k.merge(b,s,n[r].exports);if(a&&f.compiles)for(l=f.compiles&&[],c=0;r=f.compiles[c++];)l.push(n[r].exports);e(h,p,s,l)}else if(f=Error('Tried to invoke non-existent module "'+h+'"'),a)a(f);else throw f;}}();y.amd={};var B=function(){var e={},d=document.getElementsByTagName("head")[0],g="undefined"!==typeof opera&&"[object Opera]"===opera.toString(),h=function(a){var b=document.createElement("script");
|
||||
b.setAttribute("id",a);b.type="text/javascript";b.charset="utf-8";b.async=!0;return b},c=!(!(c=h())||!c.attachEvent||c.attachEvent.toString&&0>c.attachEvent.toString().indexOf("[native code")||g),a=function(){var a={"http://":1,"https://":1,"file://":1};return function(b){var c=b.toLowerCase(),e=c[0]||c.slice(0,1);"/"===e||"\\"===e||"h"===e&&(a[c.slice(0,7)]||a[c.slice(0,8)])||"f"===e&&a[c.slice(0,7)]||(b=x.baseUrl+b);".js"!==b.slice(-3)&&(b+=".js");return b}}(),b=function(a,b,c,d){return function(f){e[b]=
|
||||
0;a.error=f||!0;d.parentElement.removeChild(d);if(a.onError)a.onError(f,c,b)}},f=function(a,b,c,d,f){return function(){if((!f||"loaded"===d.readyState||"complete"===d.readyState)&&2===e[b]){e[b]=3;if(a.onPartial)a.onPartial(b,c,d);f?d.detachEvent("onreadystatechange",a.loadListener):(d.removeEventListener("load",a.loadListener),d.removeEventListener("error",a.errorListener));if(a.loaded.push(b)===a.paths.length&&a.onDone)a.onDone(a.load)}}},m=function(a){for(var g=0,k,m,n;!a.error&&(k=a.paths[g++]);)m=
|
||||
a.names[g-1],e[k]=2,n=h(k),c?n.attachEvent("onreadystatechange",f(a,k,m,n,!0)):(a.loadListener=f(a,k,m,n),a.errorListener=b(a,k,m,n),n.addEventListener("load",a.loadListener,!1),n.addEventListener("error",a.errorListener,!1)),n.src=k,d.appendChild(n)};return function(b,c,d,f){var g=[],h=[],t={paths:[],names:[],loaded:[],error:null,loadListener:null,errorListener:null},v,u,w,z,A;t.onError=function(a,b,c){a instanceof Error||(a=Error('Could require "'+b+'"; an error occurred while trying to load "'+
|
||||
c+'".'));if(d)d(a);else throw a;};for(A=0;u=b[A++];)if(n[u])if(3===n[u].state)h.push(n[u].exports);else{if(2===n[u].state)throw Error("require(): Rejected dependency. Cannot continue.");F(u,null,t.onError);g.push(u)}else v=a(x.paths[u]||u),e[v]||(t.paths.push(v),t.names.push(u),e[v]=2),g.push(u);g.length?(t.paths&&(t.onPartial=function(a,b,c){n[b]||((w=x.shim[b])?(z=k.is(w.exports,"function")?w.exports:function(){return q[w.exports]},y(b,z,t.onError)):y(E,b,t.onError));F(b,null,t.onError);f&&f(a,
|
||||
c)},m(t)),G(g,function(a){c.apply(null,a)},t.onError)):c&&c.apply(null,h)}}();y("fandango",k);var I=q.fandango,J=q.define,K=q.require,w={fn:k,define:y,require:B,noConflict:function(e){q.define===y&&(q.define=J);q.require===B&&(q.require=K);e&&q.fandango===w&&(q.fandango=I);return w},defaults:function(e){return e?k.merge(x,e):x}};q.define||(q.define=w.define);q.require||(q.require=w.require);q.fandango=w;q.modules=n;q.awaitingDependency=v;q.awaitingName=z})(this);
|
2
public/js/vendor/socket.io.v0.9.16.min.js
generated
vendored
14
rollup.config.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { nodeResolve } from '@rollup/plugin-node-resolve';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
|
||||
module.exports = {
|
||||
input: 'client/source/cryptalk.js',
|
||||
output: {
|
||||
file: 'client/public/js/cryptalk.js',
|
||||
format: 'iife'
|
||||
},
|
||||
plugins: [
|
||||
nodeResolve(),
|
||||
commonjs()
|
||||
]
|
||||
};
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
91
server.js
|
@ -1,91 +0,0 @@
|
|||
var express = require('express.io'),
|
||||
uuid = require('node-uuid'),
|
||||
|
||||
app = express();app.http().io();
|
||||
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
|
||||
app.io.route('room', {
|
||||
generate: function(req) {
|
||||
var room = uuid.v4();
|
||||
req.socket.emit('room:generated',room);
|
||||
},
|
||||
join: function(req) {
|
||||
if( req.data ) {
|
||||
req.socket.emit('room:joined',req.data);
|
||||
req.socket.join(req.data);
|
||||
req.socket.broadcast.to(req.data).emit('message:server', {msg:'person_joined'} );
|
||||
req.socket.current_room = req.data;
|
||||
} else {
|
||||
req.socket.emit('message:server', {msg:'command_failed'} );
|
||||
}
|
||||
},
|
||||
leave: function(req) {
|
||||
if( req.data ) {
|
||||
req.socket.emit('room:left');
|
||||
req.socket.leave(req.data);
|
||||
req.socket.broadcast.to(req.data).emit('message:server', {msg:'person_left'} );
|
||||
req.socket.current_room = undefined;
|
||||
} else {
|
||||
req.socket.emit('message:server', {msg:'command_failed'} );
|
||||
}
|
||||
},
|
||||
count: function(req) {
|
||||
if( req.socket.current_room !== undefined ) {
|
||||
// This will fail on socket.io >= 1.0
|
||||
var client_count = app.io.sockets.clients(req.socket.current_room).length;
|
||||
req.socket.emit('message:server', {msg:'person_count', payload: client_count } );
|
||||
} else {
|
||||
req.socket.emit('message:server', {msg:'command_failed'} );
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.io.route('message', {
|
||||
send: function(req) {
|
||||
|
||||
// Check that the user is in a room
|
||||
if(req.data && req.data.room) {
|
||||
|
||||
// Check that the message size is within bounds
|
||||
var total_msg_size = (req.data.msg) ? req.data.msg.length : 0 + (req.data.nick) ? req.data.nick.length : 0;
|
||||
if( total_msg_size <= 4096) {
|
||||
|
||||
// Check that at least 100ms has passed since last message
|
||||
if( req.socket.last_message === undefined || new Date().getTime() - req.socket.last_message > 100 ) {
|
||||
|
||||
req.socket.broadcast.to(req.data.room).emit('message:send', { msg: req.data.msg, nick: req.data.nick} );
|
||||
req.socket.emit('message:send', { msg: req.data.msg, nick: req.data.nick} );
|
||||
|
||||
req.socket.last_message = new Date().getTime();
|
||||
|
||||
} else {
|
||||
|
||||
// Do not complain if message rate is too fast, that would only generate more traffic
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Message size is out of bounds, complain
|
||||
req.socket.emit('message:server', {msg:'command_failed'} );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.io.sockets.on('connection', function(socket) {
|
||||
socket.on('disconnect', function() {
|
||||
// Notify other users of the room
|
||||
if( socket.current_room !== undefined ) {
|
||||
socket.broadcast.to(socket.current_room).emit('message:server', {msg:'person_left'} );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(8080, function(){
|
||||
console.log('listening on *:8080');
|
||||
});
|
107
server/server.js
Normal file
|
@ -0,0 +1,107 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
const
|
||||
handler = require('serve-handler'),
|
||||
port = process.env.PORT || 8080,
|
||||
path = require('path');
|
||||
|
||||
let
|
||||
server,
|
||||
io;
|
||||
|
||||
// Create http server, handle files.assets
|
||||
server = require('http').createServer(function (req, res) {
|
||||
return handler(req, res, {
|
||||
public: path.resolve(__dirname, '../client/public')
|
||||
});
|
||||
});
|
||||
|
||||
// Append socket.io to http server
|
||||
io = require('socket.io')(server),
|
||||
|
||||
// Listen to port env:PORT or 8080
|
||||
server.listen(port, function(){
|
||||
console.log('listening on *:' + port); // eslint-disable-line no-console
|
||||
});
|
||||
|
||||
io.on('connection', function(socket) {
|
||||
|
||||
socket.on('room:join', function(req) {
|
||||
if( req ) {
|
||||
socket.emit('room:joined',req);
|
||||
socket.join(req);
|
||||
socket.broadcast.to(req).emit('message:server', {msg:'person_joined'} );
|
||||
socket.current_room = req;
|
||||
} else {
|
||||
socket.emit('message:server', {msg:'command_failed'} );
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('room:leave', function(req) {
|
||||
if( req ) {
|
||||
socket.emit('room:left');
|
||||
socket.leave(req);
|
||||
socket.broadcast.to(req).emit('message:server', {msg:'person_left'} );
|
||||
socket.current_room = undefined;
|
||||
} else {
|
||||
socket.emit('message:server', {msg:'command_failed'} );
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('room:count', function () {
|
||||
if( socket.current_room !== undefined ) {
|
||||
let clientsInRoom = 0;
|
||||
if( io.sockets.adapter.rooms.has(socket.current_room) ) {
|
||||
clientsInRoom = io.sockets.adapter.rooms.get(socket.current_room).size;
|
||||
}
|
||||
if( clientsInRoom > 1) {
|
||||
socket.emit('message:server', {msg:'person_count', payload: clientsInRoom } );
|
||||
} else {
|
||||
socket.emit('message:server', {msg:'person_single'} );
|
||||
}
|
||||
} else {
|
||||
socket.emit('message:server', {msg:'command_failed'} );
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('message:send', function(req) {
|
||||
|
||||
// Check that the user is in a room
|
||||
if(req && req.room) {
|
||||
|
||||
// Check that the message size is within bounds
|
||||
var total_msg_size = (req.msg) ? req.msg.length : 0 + (req.nick) ? req.nick.length : 0;
|
||||
if( total_msg_size <= 4096) {
|
||||
|
||||
// Check that at least 100ms has passed since last message
|
||||
if( socket.last_message === undefined || new Date().getTime() - socket.last_message > 100 ) {
|
||||
|
||||
socket.broadcast.to(req.room).emit('message:send', { msg: req.msg, nick: req.nick} );
|
||||
socket.emit('message:send', { msg: req.msg, nick: req.nick} );
|
||||
|
||||
socket.last_message = new Date().getTime();
|
||||
|
||||
} else {
|
||||
|
||||
// Do not complain if message rate is too fast, that would only generate more traffic
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Message size is out of bounds, complain
|
||||
socket.emit('message:server', {msg:'command_failed'} );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
socket.on('disconnect', function() {
|
||||
// Notify other users of the room
|
||||
if( socket.current_room !== undefined ) {
|
||||
socket.broadcast.to(socket.current_room).emit('message:server', {msg:'person_left'} );
|
||||
}
|
||||
});
|
||||
|
||||
});
|