Compare commits

...

129 commits

Author SHA1 Message Date
d3c07e19b8 Add cryptalk.service
Unit file for the Claytonia server.
2024-02-01 19:06:53 -05:00
6b6a25f28a Update 'README.md'
Changed screenshot
2023-11-13 17:34:53 -05:00
d2b779540b Update 'client/public/js/cryptalk.min.js'
Claytonia branding.
Added more help
2023-11-13 17:33:52 -05:00
3dda921db2 Update 'client/source/settings.js'
Claytonia branding 
Added more help
2023-11-13 17:33:01 -05:00
Hexagon
a9c0e7592e
Update FUNDING.yml 2023-02-03 12:19:22 +01:00
Hexagon
8ceb715798
Dependency update. Bump. 2022-03-30 00:45:02 +02:00
Hexagon
558b2dff35 Use PM2 for docker main process. Update deps. Bump. 2022-02-21 00:00:27 +01:00
Hexagon
c91a1c105b Dependency update 2022-01-31 21:04:22 +01:00
Hexagon
2197a1ef46 Dependency update, close #36. 2022-01-02 21:27:58 +01:00
Hexagon
d6b5baeb7c
Update README.md 2021-12-31 17:37:29 +01:00
Hexagon
2b7e5bcffe Update dev dependencies 2021-12-25 21:35:32 +01:00
Hexagon
d4e6c5cdb1 Update dependencies, bump version. 2021-12-12 21:14:18 +01:00
Hexagon
82a498a4ff Cleanup 2021-11-09 23:43:21 +01:00
Hexagon
6e8816c467 Dependency bump (serve 12->13). Add dependency checks to local build pipeline, do not run on ci builds. 2021-11-07 13:52:45 +01:00
Hexagon
28a8cf8b31
Create dependabot.yml 2021-11-07 13:20:31 +01:00
Hexagon
cd4e146aa9 Fix issue #4 2021-11-03 21:40:15 +01:00
Hexagon
2f74ef5ca7 CRLF -> LF in docker-entrypoint 2021-11-03 21:29:56 +01:00
Hexagon
9087608087 Minor fix 2021-11-03 21:17:16 +01:00
Hexagon
ec66f19932 Update readme 2021-11-03 21:05:59 +01:00
Hexagon
57345250bd Remove Node 10.x as development target 2021-11-03 21:01:24 +01:00
Hexagon
5af420d411 Update package-lock.json 2021-11-03 20:58:19 +01:00
Hexagon
26b3661017 es6 cleanup + include castrato for now 2021-11-03 20:57:54 +01:00
Hexagon
47687efa9f Initial convert to es6 2021-11-03 20:35:57 +01:00
Hexagon
e4920b542c
Create codeql-analysis.yml 2021-10-19 22:21:56 +02:00
Hexagon
6b88e32cb0
Create SECURITY.md 2021-10-19 22:21:38 +02:00
Hexagon
a555afd065 Update to node 16 in docker build 2021-10-19 20:55:29 +02:00
Hexagon
a07ab11f8f Updating dependencies 2021-10-19 20:52:26 +02:00
Hexagon
d8706f18e5 Merge branch 'master' of https://github.com/Hexagon/cryptalk 2021-10-19 20:44:44 +02:00
Hexagon
8ab38d8a41 Cleanup 2021-10-19 20:44:31 +02:00
Hexagon
699e2e0ed2
Create FUNDING.yml 2021-10-17 11:37:19 +02:00
Hexagon
74bdf5dfef Lint 2021-10-07 21:54:51 +02:00
Hexagon
5b248f46dd NPM scripts 2021-10-03 21:20:27 +02:00
Hexagon
ed04c1510c No tests, but that's ok 2021-10-03 01:05:05 +02:00
Hexagon
18e601de20
Update .travis.yml 2021-10-03 00:59:22 +02:00
Hexagon
b2a2055b99
Update README.md 2021-10-03 00:58:21 +02:00
Hexagon
175022f041 Node 14 2021-10-02 22:00:50 +02:00
Hexagon
baa9601db5 socket.io 2 -> 4, node-static replaced with serve 2021-10-02 21:34:22 +02:00
Hexagon
f7a44991a4
Merge pull request #15 from Hexagon/dependabot/npm_and_yarn/ws-7.4.6
Bump ws from 7.4.2 to 7.4.6
2021-08-31 20:54:58 +02:00
dependabot[bot]
aad78afe82
Bump ws from 7.4.2 to 7.4.6
Bumps [ws](https://github.com/websockets/ws) from 7.4.2 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.4.2...7.4.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-29 15:37:48 +00:00
Hexagon
975dc18377
Merge pull request #13 from Hexagon/dependabot/npm_and_yarn/socket.io-2.4.0
Bump socket.io from 2.2.0 to 2.4.0
2021-01-24 13:14:08 +01:00
dependabot[bot]
d5ac8ff58c
Bump socket.io from 2.2.0 to 2.4.0
Bumps [socket.io](https://github.com/socketio/socket.io) from 2.2.0 to 2.4.0.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/2.4.0/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/2.2.0...2.4.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-21 11:15:39 +00:00
Hexagon
189aac54a5
Merge pull request #11 from nwithan8/patch-2
New person_single message for only one person in a room
2019-11-10 21:17:51 +01:00
Nate Harris
fb190db4c8
Update server.js 2019-11-05 23:00:23 -05:00
Nate Harris
1d355b0271
New person_single message for only one person in a room 2019-11-05 22:55:56 -05:00
Hexagon
6ae2183385 Release 1.1.21 2019-09-17 19:21:35 +01:00
Hexagon
78acd03746 Update travis target versions 2019-09-16 19:45:34 +01:00
Hexagon
9b484d4158 Update dependencies, bump to 1.1.20 2019-09-16 19:34:18 +01:00
Hexagon
5089974c22 Update dependencies, bump to 1.1.20 2019-09-16 19:33:40 +01:00
Hexagon
94ae46ee9a Minor fix 2017-12-04 22:12:04 +01:00
Hexagon
4fff368db7
Merge pull request #7 from lapwat/master
resolved npm cache clean error & created forgotten directory
2017-11-29 18:09:27 +01:00
Quentin Lapointe
86abee6965 resolved npm cache clean error & created forgotten directory 2017-11-01 17:47:54 +01:00
Hexagon
6775b427a5 1.1.18 Pre release lint 2017-02-22 23:09:50 +01:00
Hexagon
5015caf55a Finishing the move to requirejs 2017-02-22 22:22:28 +01:00
Hexagon
fd8e8a9915 Minor adjustments 2017-02-08 21:24:58 +01:00
Hexagon
1cf7a8f4c6 Converting to RequireJS 2017-02-08 00:02:12 +01:00
Hexagon
9e985b05f0 Ooops 2017-01-23 23:43:22 +01:00
Hexagon
9583845466 static was used as variable name, not ok. Release 1.1.17 2017-01-23 23:42:03 +01:00
Hexagon
721e80aa1f Adhering to change in socket.io, Release 1.1.16 2017-01-23 22:29:23 +01:00
Hexagon
9e16242c2c Static file serving were broken. Release 1.1.15 2017-01-18 00:07:06 +01:00
Hexagon
005ad02f68 Replacing express with node-static. Releasing 0.0.14 2017-01-17 23:52:55 +01:00
Hexagon
4f0016e150 Minimizing Dockerfile 2017-01-17 22:53:57 +01:00
Hexagon
ddd1005141 Minimizing Dockerfile. 1.1.13 2017-01-17 22:50:05 +01:00
Hexagon
799b6c5b84 Ignored too much. Release 1.1.12 2017-01-17 22:35:30 +01:00
Hexagon
f5c6a3b2b5 Updating ignorefiles. Documentation. Release 1.1.11. 2017-01-17 22:30:16 +01:00
Hexagon
7dc1d385ab Fixing docker entrypoint, 1.1.10 2017-01-15 22:28:34 +01:00
Hexagon
dc1e7091f3 Ok, one more 2017-01-15 01:05:35 +01:00
Hexagon
2eb2c8ad31 Last try 2017-01-15 01:01:35 +01:00
Hexagon
ab532cb938 Permissions 2017-01-15 00:54:04 +01:00
Hexagon
133f9e094c Fixed entrypoint 2017-01-15 00:49:59 +01:00
Hexagon
e09b3ee92f Added entrypoint (docker) 2017-01-15 00:47:20 +01:00
Hexagon
1bf3400ba3 Trying again 2017-01-15 00:33:10 +01:00
Hexagon
a6adc87e08 Hmm, reverting, 1.1.8 2017-01-15 00:09:10 +01:00
Hexagon
d55ae52565 Minor deploy fix (docker), 1.1.7 2017-01-15 00:03:49 +01:00
Hexagon
60b24afdbf Make public/ a docker volume, 1.1.6
... so that another server can host the static files.
2017-01-14 23:45:13 +01:00
Hexagon
2f86cffc6e Minor documentation fix, 1.1.5 2017-01-14 23:37:43 +01:00
Hexagon
4cb0798467 Docker: node:argon -> node:alpine 2016-12-19 00:40:59 +01:00
Hexagon
14ea29b81a Update README.md 2016-12-18 23:55:28 +01:00
Hexagon
14c2e2ff42 Docker instructions, release 1.1.4 2016-12-18 23:51:29 +01:00
Hexagon
5082ba7680 Adding dockerfile 2016-12-18 00:03:43 +01:00
Robin Nilsson
67538fdb7f Update package.json 2016-01-05 00:15:48 +01:00
Robin Nilsson
c417f6b5d3 Update README.md 2016-01-05 00:15:24 +01:00
Hexagon
edbc9ce43d Documentation. Changed key.maxLen from Infinity to 1024 bytes 2016-01-05 00:08:15 +01:00
Hexagon
a13dd84d21 Classic oops ... 2016-01-04 01:36:21 +01:00
Hexagon
fdd57e3f11 Various
* Socket.io 0.9.16 -> 1.3.7
* Ditched express.io
* Fixed non prominent bugs in client
* Socket.io connect errors is now showing
* Minor code cleanups
* Bumped minor version
* Added additional keywords to package.json
2016-01-04 01:34:16 +01:00
Hexagon
0c28ef6a65 More fixes 2016-01-02 19:14:25 +01:00
Hexagon
10384f956d Fixes + auto-torch after configurable delay 2016-01-02 19:13:25 +01:00
Hexagon
dd8529bd88 Oops 2015-12-30 00:42:00 +01:00
Hexagon
889e46b291 Fixes 2015-12-30 00:38:05 +01:00
Hexagon
8068c81e2e Fixed participant count 2015-10-04 14:44:40 +02:00
Hexagon
c0408a614f Typo 2015-10-04 14:24:55 +02:00
Hexagon
96839665e3 Minor cleanup 2015-10-04 14:13:24 +02:00
Robin Nilsson
99e9978fb2 OMG. Swedish röck döts in the readme! 2014-10-21 21:19:36 +02:00
Robin Nilsson
bbee06f7d5 Update .travis.yml 2014-10-20 21:04:05 +02:00
Hexagon
ed43e64a75 Preparing for automated testing 2014-10-20 21:02:19 +02:00
Hexagon
1fba5fb0e3 Build icon 2014-10-20 20:57:50 +02:00
Hexagon
9d875524ec Support heroku deploy 2014-10-20 20:37:55 +02:00
Hexagon
af79206742 Version bump 2014-10-20 19:53:47 +02:00
Hexagon
3461547dec making it work 2014-10-20 19:14:44 +02:00
unkelpehr
e221e9b662 Bughunt 2014-09-27 14:56:57 +02:00
unkelpehr
cb37d41502 Bughunt 2014-09-27 14:44:41 +02:00
unkelpehr
276220eaf4 Bughunt 2014-09-27 14:40:08 +02:00
unkelpehr
65d8a1c122 Lintin' lintin' lintin' 2014-09-27 14:22:57 +02:00
unkelpehr
a72cd394ad (Partial) added /require command and debug module 2014-09-27 14:20:27 +02:00
unkelpehr
b0d45d4b69 Updated vendors Castrato and fandango. 2014-09-27 14:20:26 +02:00
Hexagon
0ff7bc0ed7 Dang 2014-09-26 23:14:46 +02:00
Hexagon
f491373abe Timestamps 2014-09-26 23:13:37 +02:00
Hexagon
1ad2108e71 Mostly done! 2014-09-26 18:21:27 +02:00
Hexagon
4f4f7c4325 Major move towards castrato awareness 2014-09-26 14:46:35 +02:00
Robin Nilsson
507df4ea58 Published v1.0.4 2014-09-25 23:46:15 +02:00
Robin Nilsson
fb38738d2f Various fixes
Configurable mininum delay between notifications added
Grouped settings
Configurable default + host specific title
win -> window
removed /generate-command
deprecated dependency on node-uuid
2014-09-25 23:43:24 +02:00
Robin Nilsson
1216332270 Update README.md 2014-09-25 16:29:04 +02:00
Robin Nilsson
99a37e0547 Update package.json 2014-09-25 16:27:58 +02:00
Robin Nilsson
30937e566b Add shebang for npm install script 2014-09-25 16:27:33 +02:00
unkelpehr
69775a0f42 Revert "Added vendor repository"
This reverts commit c855c39b33.
2014-09-25 15:35:27 +02:00
unkelpehr
4060be4bc0 Revert "Please work"
This reverts commit 2f103b849b.
2014-09-25 14:51:40 +02:00
unkelpehr
2f103b849b Please work 2014-09-25 14:43:35 +02:00
unkelpehr
c855c39b33 Added vendor repository 2014-09-25 14:04:54 +02:00
unkelpehr
f4277cb0f5 Revert "Added vendor repository castrato"
This reverts commit 1ec673b249.
2014-09-25 13:55:29 +02:00
unkelpehr
1ec673b249 Added vendor repository castrato 2014-09-25 13:36:12 +02:00
unkelpehr
37775bd4e2 Revert "hmpf"
This reverts commit 4d927ea74f.
2014-09-25 13:29:19 +02:00
unkelpehr
4d927ea74f hmpf 2014-09-25 13:11:28 +02:00
unkelpehr
f90679703c Revert "Added vendor castrato"
This reverts commit e8a961f4a3.
2014-09-25 13:09:25 +02:00
Robin Nilsson
b11f942272 Update package.json 2014-09-25 13:08:34 +02:00
Robin Nilsson
785c8a08f1 Update README.md 2014-09-25 13:08:11 +02:00
Robin Nilsson
0b7b87841b Update package.json 2014-09-25 13:05:06 +02:00
unkelpehr
5d3e723b3d Merge branch 'master' of https://github.com/Hexagon/cryptalk 2014-09-25 13:03:01 +02:00
unkelpehr
e8a961f4a3 Added vendor castrato 2014-09-25 13:02:34 +02:00
Robin Nilsson
0eaa5c92a7 Update package.json 2014-09-25 13:02:22 +02:00
Robin Nilsson
fc8831d017 Update package.json 2014-09-25 12:52:25 +02:00
61 changed files with 5632 additions and 1648 deletions

16
.dockerignore Normal file
View 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
View file

@ -0,0 +1,2 @@
client/public/js/cryptalk.min.js
node_modules

38
.eslintrc.json Normal file
View 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
View file

@ -0,0 +1,2 @@
github: [hexagon]
ko_fi: hexagon_56k

6
.github/dependabot.yml vendored Normal file
View 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
View 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
View 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
View file

@ -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
View 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
View 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" ]

View file

@ -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
View file

@ -1,39 +1,120 @@
![cryptalk](/docs/screenshot.png)
![cryptalk](https://i.imgur.com/1sH36n5.png)
Cyptalk is a HTML5/Node.js based encrypted instant chat
![Node.js CI](https://github.com/Hexagon/cryptalk/workflows/Node.js%20CI/badge.svg?branch=master)
[![npm version](https://badge.fury.io/js/cryptalk.svg)](https://badge.fury.io/js/cryptalk)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/753ef40cec1747c2b5025f834635375b)](https://www.codacy.com/gh/Hexagon/cryptalk/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=Hexagon/cryptalk&amp;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
[![Deploy](https://www.herokucdn.com/deploy/button.png)](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
View 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
View 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"
}

View file

@ -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); }
}

View file

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

View file

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

View file

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

View file

Before

Width:  |  Height:  |  Size: 628 B

After

Width:  |  Height:  |  Size: 628 B

View file

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

36
client/source/$.js Normal file
View 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
View 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
View 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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
'\'': '&#39;',
'/': '&#x2F;'
};
return function (string) {
return String(string).replace(pattern, function (s) {
return entities[s];
});
};
}());
export default exports;

89
client/source/audio.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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);
}

View file

@ -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
View 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
View 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
View 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
}
};

View file

@ -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]
]
};

View file

@ -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&gt; <i class="info">{text}</i></li>',
server: '<li>SRV&gt; <i class="server">{text}</i></li>',
error: '<li>ERR&gt; <i class="error">{text}</i></li>',
message: '<li><i class="nick">{nick}&gt;</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&gt; <i class="info">{text}</i></li>',
server: '<li id="{id}"><i class="timestamp">[{timestamp}] </i>SRV&gt; <i class="server">{text}</i></li>',
error: '<li id="{id}"><i class="timestamp">[{timestamp}] </i>ERR&gt; <i class="error">{text}</i></li>',
message: '<li id="{id}"><i class="timestamp">[{timestamp}] </i>MSG&gt; <i class="nick">{nick}&gt;</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
View 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 };

View file

@ -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
View 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

File diff suppressed because it is too large Load diff

View file

@ -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
View file

@ -0,0 +1,11 @@
{
"name": "cryptalk",
"script": "server/server.js",
"instances": "1",
"env": {
"NODE_ENV": "development"
},
"env_production" : {
"NODE_ENV": "production"
}
}

View file

@ -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;
});

View file

@ -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 = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;'
};
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;
})

View file

@ -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(); });
});

View file

@ -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]);
}
});
});

View file

@ -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'
}*/
]
});

View file

@ -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;
}
};
};
}())));

View file

@ -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;
});

View file

@ -1,7 +0,0 @@
define({
nick_maxLen: 20,
nick_minLen: 3,
key_maxLen: Infinity,
key_minLen: 8
});

View file

@ -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)})();

View file

@ -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});

View file

@ -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);

File diff suppressed because one or more lines are too long

14
rollup.config.js Normal file
View 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()
]
};

View file

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -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
View 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'} );
}
});
});