Compare commits

..

81 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
60 changed files with 5585 additions and 1701 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

3
.gitignore vendored
View file

@ -27,3 +27,6 @@ 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

View file

@ -1,7 +0,0 @@
language: node_js
node_js:
- "0.10"
- "0.11"
- "0.12"
- "4.0"
- "4.1"

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,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014 Robin Nilsson
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

115
README.md
View file

@ -1,36 +1,61 @@
![cryptalk](/docs/screenshot.png)
![cryptalk](https://i.imgur.com/1sH36n5.png)
![Build](https://travis-ci.org/Hexagon/cryptalk.svg)
![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)
# Cryptalk
Cyptalk is a HTML5/Node.js based, client side (E2EE) encrypted instant chat
## Features
Features
========
* 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
* Client side AES-256-CBC encryption/decryption (the server is just a messenger)
* 256 bit key derived from your passphrase using PBKDF2
* Message is torched after a configurable delay, default is 600s.
* Optional nicknames
* Quick-links (not recommended!) using http://server/#Room:Passphrase
* Super simple setup
* Notification sounds (mutable)
* Native popup notifications
* Configurable page title
* Heroku support
## 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
Heroku setup
========
```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
Regular setup
========
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.
@ -49,32 +74,7 @@ Browse to ```http://localhost:8080```
Done!
Developer setup
========
Install node.js, exact procedure is dependant on platform and distribution.
Clone this repo
```bash
git clone https://github.com/Hexagon/cryptalk.git
cd cryptalk
```
Pull dependencies from npm
```bash
npm install
```
Start server
```bash
node server.js
```
Browse to ```http://localhost:8080```
Usage
========
## Usage
```
@ -98,8 +98,7 @@ Room:
/count Count participants
Host:
/hosts List available hosts
/connect HostIndex Connect to selected host
/connect Connect to host
/disconnect Disconnect from host
You can select any of the five last commands/messages with up/down key.
@ -112,3 +111,27 @@ 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
git clone https://github.com/Hexagon/cryptalk.git
cd cryptalk
```
Pull dependencies from npm
```bash
npm install
```
Start server
```bash
npm run start
```
Browse to ```http://localhost:8080```
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.

View file

@ -3,13 +3,14 @@
"description": "Client side (E2EE) encrypted instant chat",
"keywords": [
"cryptalk",
"fandango",
"crypto-js",
"AES",
"secure",
"html5",
"encryption",
"privacy"
"privacy",
"chat",
"e2ee"
],
"repository": "https://github.com/Hexagon/cryptalk"
}

View file

@ -1,32 +1,31 @@
/*------------------------------------*\
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+ */
}
*, *: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; }
/*------------------------------------*\
@ -38,19 +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: #606006;
color: #808008;
}
#chat li .timestamp {
color: #808008;
color: #a0a00a;
}
/* Message types */
@ -58,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,
@ -90,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

@ -7,6 +7,7 @@
<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>
@ -22,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 );

View file

@ -17,22 +17,14 @@
*/
define(
{
compiles: ['$'],
requires: ['castrato','settings','templates']
}, function ($, requires, data) {
import $ from './$.js';
export default function (mediator, settings, templates) {
var
// Private properties
nick,
key,
// Require shortcuts
mediator = requires.castrato,
settings = requires.settings,
templates = requires.templates,
setKey = function(payload) {
/*if (!host) {
return post('error', templates.messages.key_no_host);
@ -61,20 +53,20 @@ define(
setTorch = function (payload) { mediator.emit('console:torch',payload); },
nick = function (payload) {
setNick = function (payload) {
// Make sure the nick meets the length requirements
if (payload.length > settings.nick.maxLen) {
return mediator('console:error', $.template(templates.messages.nick_to_long, { nick_maxLen: 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('console:error', $.template(templates.messages.nick_to_short, {nick_minLen: 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);
mediator.emit('nick:changed', nick);
// Inform that the nick has been set
mediator.emit('console:info', $.template(templates.messages.nick_set, { nick: $.escapeHtml(nick)}));
@ -88,9 +80,8 @@ define(
mediator.on('command:help', help);
mediator.on('command:clear', clear);
mediator.on('command:nick', nick);
mediator.on('command:nick', setNick);
mediator.on('command:key', setKey);
mediator.on('command:torch', setTorch);
mediator.on('command:title', title);
});
}

View file

@ -17,19 +17,11 @@
mediator.emit('audio:play',...);
ToDo
*/
define({
compiles: ['$'],
requires: ['castrato', 'fandango', 'settings', 'templates', 'sounds', 'room', 'notifications', 'audio']
}, function ($, requires, data) {
import $ from './$.js';
var // Require shortcuts
fandango = requires.fandango,
mediator = requires.castrato,
settings = requires.settings,
templates = requires.templates,
sounds = requires.sounds,
export default function(mediator,settings,templates, sounds) {
// Collection of DOM components
var // Collection of DOM components
components = {
chat: $('#chat'),
input: $('#input'),
@ -45,7 +37,7 @@ define({
var tpl = templates.post[type],
uniqueId = 'msg_' + new Date().getTime() + '_' + Math.round(Math.random()*1000000),
post,
data = fandango.merge({}, settings, {
data = Object.assign({}, settings, {
nick: nick,
timestamp: new Date().toLocaleTimeString(),
id: uniqueId
@ -69,7 +61,7 @@ define({
},
torch: function (ttl) {
var ttl = parseInt(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;
@ -79,12 +71,12 @@ define({
},
param: function (p) {
parameters = fandango.merge({}, parameters, 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');
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', {
@ -120,15 +112,11 @@ define({
},
clearInput: function () {
fandango.subordinate(function () {
components.input[0].value = '';
});
},
clear: function () {
fandango.subordinate(function () {
components.chat[0].innerHTML = '';
});
},
lockInput: function () {
@ -140,20 +128,6 @@ define({
components.input[0].removeAttribute('disabled');
components.inputWrapper[0].className = '';
components.input.focus();
},
_require: function (filepath, done) {
commands.lockInput();
commands.post('info', 'Requiring ' + filepath + '...');
require([filepath], function () {
commands.post('info', 'Successfully required ' + filepath + '.');
commands.unlockInput();
done();
}, function (e) {
commands.post('error', 'An error occurred while trying to load "' + filepath + '":\n' + e);
commands.unlockInput();
done();
});
}
},
@ -162,14 +136,14 @@ define({
var buffer,
parts,
payload,
command,
save;
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()) {
return components.input.focus();
components.input.focus();
return;
}
// Return immediatly if the buffer is empty or if the hit key was not <enter>
@ -189,7 +163,8 @@ define({
payload,
function(retvals, recipients) {
if(!recipients) {
return commands.post('error', $.template(templates.messages.unrecognized_command, { commandName: command }));
commands.post('error', $.template(templates.messages.unrecognized_command, { commandName: command }));
return;
} else {
commands.clearInput();
}
@ -230,14 +205,12 @@ define({
// Connect events
for (var commandName in commands) {
if (commandName !== '_require' && commandName !== 'post') {
if (commandName !== 'post') {
mediator.on('console:' + commandName, commands[commandName]);
}
}
mediator.on('console:require', commands._require);
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();
});
}
}

View file

@ -2,32 +2,27 @@
Accepts:
mediator.on('command:host', host);
mediator.on('command:hosts', hosts);
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"] }]
*/
define(
{
compiles: ['$'],
requires: ['fandango','castrato','settings','templates','hosts','window']
}, function ($, requires, data) {
import $ from './$.js';
export default function (mediator, settings, templates) {
var
// Private properties
socket,
host,
// Require shortcuts
fandango = requires.fandango,
mediator = requires.castrato,
settings = requires.settings,
templates = requires.templates,
hostconfig = requires.hosts,
host = {
host: '',
connected: false
},
// Collection of parameters
parameters = {},
@ -37,104 +32,27 @@ define(
if(socket) socket.emit(payload.data,payload.payload);
},
host = function () {
hostInfo = function () {
mediator.emit('info', JSON.stringify(host || {}));
},
hosts = function (force, done) {
var i = 0,
left = hostconfig.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) {
mediator.emit('console:info', strhosts);
done();
}
};
};
force = (force && force.toLowerCase() === 'force');
// Loop through all the hosts
while ((host = hostconfig.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) {
mediator.emit('console:lockInput');
var
request,
// Use hostconfig.autoconnect as default host
toHost = (toHost == undefined) ? hostconfig.autoconnect : toHost;
if (host && host.connected) {
mediator.emit('console:error', $.template(templates.messages.already_connected, {
host: host.name || 'localhost'
host: host.host
}));
mediator.emit('console:unlockInput');
return;
}
if ($.isDigits(toHost)) {
if ((host = hostconfig.hosts[+toHost])) {
if (host.settings) {
settings = host.settings;
} else {
request = host.path;
}
} else {
mediator.emit('console:error', 'Undefined host index: ' + toHost);
mediator.emit('console:unlockInput');
return;
}
} else if (fandango.is(toHost, 'untyped')) {
settings = toHost.settings;
} else { // Assume string
request = toHost.settings;
}
if (request) {
return require([request], function (settings) {
host.settings = settings;
return connect(toHost, done);
}, function () {
mediator.emit('console:error', 'Could not fetch host settings: ' + request);
mediator.emit('console:unlockInput');
return;
});
}
// Push 'Connecting...' message
mediator.emit('console:info', $.template(templates.messages.connecting, {host: host.name || 'localhost'}));
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', host.settings.motd);
mediator.emit('console:motd', settings.motd);
// The one and only socket
socket = $.io(host.host, {
@ -144,7 +62,7 @@ define(
// Bind socket events
socket
.on('room:joined', function (data) {
.on('room:joined', function () {
mediator.emit('console:info', $.template(templates.messages.joined_room, { roomName: $.escapeHtml(parameters.room) } ));
@ -190,29 +108,27 @@ define(
// Tell the user that the chat is ready to interact with
mediator.emit('console:info', $.template(templates.messages.connected, {
host: host.name || 'localhost'
host: host.host
}));
// Set window title
mediator.emit('window:title', host.settings.title);
mediator.emit('window:title', settings.title);
// Unlock input
mediator.emit('console:unlockInput');
done();
host.connected = 1;
host.connected = true;
})
.on('disconnect', function () {
room = 0;
key = 0;
host.connected = 0;
host.connected = false;
// Tell the user that the chat is ready to interact with
mediator.emit('console:info', $.template(templates.messages.disconnected, {
host: host.name || 'localhost'
host: host.host
}));
// Revert title
@ -222,9 +138,7 @@ define(
.on('connect_error', function () {
room = 0;
key = 0;
host.connected = 0;
host.connected = false;
mediator.emit('console:error', templates.messages.socket_error);
// Unlock input
@ -253,15 +167,14 @@ define(
},
param = function (p) {
parameters = fandango.merge({}, parameters, p );
parameters = Object.assign({}, parameters, p );
};
mediator.on('command:host', host);
mediator.on('command:hosts', hosts);
mediator.on('command:host', hostInfo);
mediator.on('command:connect', connect);
mediator.on('command:disconnect', disconnect);
mediator.on('command:reconnect', disconnect);
mediator.on('command:reconnect', reconnect);
mediator.on('socket:emit', emit);
mediator.on('host:param', param);
});
}

View file

@ -15,7 +15,9 @@ Usage
mediator.emit('notification:off');
*/
define(['castrato','window','settings'], function (mediator, win, settings) {
export default function(mediator, settings, win) {
var enabled = true,
@ -45,12 +47,11 @@ define(['castrato','window','settings'], function (mediator, win, settings) {
if (original_title !== undefined) win.setTitle(original_title);
original_title = undefined;
new_title = undefined;
window_active = true;
},
doBlink = function() {
if(enabled) {
if( win.getTitle() == original_title )
if( win.getTitle() === original_title )
win.setTitle( new_title );
else
win.setTitle( original_title);
@ -63,14 +64,12 @@ define(['castrato','window','settings'], function (mediator, win, settings) {
enableNative = function() {
if( native_supported && Notification.permission !== 'denied' ) {
Notification.requestPermission(function (status) {
Notification.permission = status;
});
Notification.requestPermission();
}
},
blinkTitleUntilFocus = function(t,i) {
interval = (i == undefined) ? 1000 : i;
interval = (i === undefined) ? 1000 : i;
if ( enabled && original_title === undefined ) {
new_title = t;
original_title = win.getTitle();
@ -86,7 +85,7 @@ define(['castrato','window','settings'], function (mediator, win, settings) {
// Set default value for fallback parameter
if ( fallback === undefined) fallback = false;
if ( native_supported && Notification.permission === "granted") {
if ( native_supported && Notification.permission === 'granted') {
// Create notification
var n = new Notification(title, {body: body, icon:icon});
@ -95,12 +94,12 @@ define(['castrato','window','settings'], function (mediator, win, settings) {
n.onshow = function () {
// Automatically close the notification after 5000ms
setTimeout(function(){n.close();},3000);
}
};
last = now();
} else if ( fallback ) {
blinkTitleUntilFocus("Attention",1000);
blinkTitleUntilFocus('Attention', 1000);
}
}
@ -123,5 +122,4 @@ define(['castrato','window','settings'], function (mediator, win, settings) {
// 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;

View file

@ -12,18 +12,12 @@
*/
define({
compiles: ['$'],
requires: ['castrato','settings','templates']
}, function ($, requires) {
import $ from './$.js';
export default function (mediator, settings, templates) {
var // Private properties
room = false,
// Require shortcuts
mediator = requires.castrato,
settings = requires.settings,
templates = requires.templates,
join = function(payload) {
if (room !== false) {
mediator.emit('console:error',
@ -72,4 +66,4 @@ define({
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,4 +1,4 @@
define({
export default {
message: [
[261.63*2,0,50],
[261.63*3,0,50],
@ -17,4 +17,4 @@ define({
[261.63*3,200,500],
[261.63*1,200,500]
]
});
};

View file

@ -1,6 +1,6 @@
// The templating function only supports variables.
// Define a variable as so: {variable_name}
define({
export default {
help: '<pre> \n' +
'Cryptalk, encrypted instant chat. \n' +
@ -25,8 +25,7 @@ define({
' /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' +
@ -86,7 +85,7 @@ define({
joined_room: 'Joined room {roomName}.',
left_room: 'Left room {roomName}.',
already_in_room: 'You are already in a room ({room}), stoopid.',
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.',
@ -104,7 +103,8 @@ define({
server: {
person_joined: 'A person joined this room.',
person_left: 'A person left this room.',
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.'
},
@ -112,4 +112,4 @@ define({
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

@ -11,8 +11,7 @@
window.setTitle(title);
*/
define(['castrato'],function (mediator){
export default function(mediator) {
var exports = {},
focusCallback = function() {
@ -26,20 +25,18 @@ define(['castrato'],function (mediator){
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);
window.addEventListener('focus', focusCallback, true);
window.addEventListener('blur', blurCallback, true);
} else {
// IE
window.observe("focusin", focusCallback);
window.observe("focusout", blurCallback);
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,41 +1,58 @@
{
"name" : "cryptalk",
"version" : "1.1.2",
"description" : "Encrypted HTML5/Node.JS instant chat",
"main" : "server.js",
"subdomain": "cryptalk",
"analyze": true,
"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",
"fandango",
"chat",
"crypto-js",
"AES",
"secure",
"html5",
"encryption",
"privacy"
"privacy",
"e2ee"
],
"author": "Hexagon <Hexagon@GitHub>",
"contributors": [{
"contributors": [
{
"name": "Pehr Boman",
"email": "unkelpehr@gmail.com"
}],
}
],
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/Hexagon/cryptalk.git"
},
"bin" : "./server.js",
"bin": "./server/server.js",
"dependencies": {
"express": "4.13.3",
"socket.io": "1.3.7"
"serve": "^13.0.2",
"socket.io": "^4.3.1"
},
"os": [
"darwin",
"linux",
"win32"
],
"devDependencies": {}
"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,38 +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: '/socket.io/socket.io.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'
},
// 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,180 +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.io = websocket;
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) {
each(this, function (element) {
element.innerHTML += string;
});
return this;
};
proto.first = function () {
return this[0];
};
// Naive implementations of .on()
proto.on = function (eventName, callback) {
each(this, function (element) {
if (element.addEventListener) {
element.addEventListener(eventName, callback, false);
} else if (element.attachEvent) {
element.attachEvent('on' + eventName, callback);
}
});
return this;
};
proto.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;
};
return exports;
})

View file

@ -1,88 +0,0 @@
/* 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
define(['queue','castrato','templates'], function (queue,mediator,templates) {
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 );
});

View file

@ -1,3 +0,0 @@
// Licenced under MIT - castrato - ©2014 Pehr Boman <github.com/unkelpehr>
(function(f,g){"function"===typeof define&&define.amd?define([],g()):"object"===typeof exports?module.exports=g:f.castrato=g})(this,function(){function f(e,a,b,c){var d=[e,b,1<b.length];(h[a]||(h[a]=[])).push(d);if(m[a])for(e=0,d=[d];(b=m[a][e++])&&(k(0,0,b[0],b[1],d),!c););}function g(e,a,b){var c=0;if(toSubs=h[a])for(;a=toSubs[c++];)a[0]!==e||b&&b!==a[1]||toSubs.splice(--c,1)}function k(e,a,b,c,d){var l=d||h[a]||[],g=l.length,f,k,n=[],p;h["*"]&&(l=l.concat(h["*"]));if(k=f=l.length)for(p=c?function(a){a&&
n.push(a);--f||(c(n,g),c=0)}:q;d=l[--k];)d[1](b,d[2]?p:f--,a);!f&&c&&c(n,g);e&&(m[a]||(m[a]=[])).push([b,c])}var r=0,h={},m={},q=function(){};return function(){var e=r++;return{emit:function(a,b,c,d){!0!==a&&!1!==a&&(d=c,c=b,b=a,a=!1);k(a,b,c,d);return this},on:function(a,b){f(e,a,b);return this},once:function(a,b){f(e,a,function d(f,h){g(e,a,d);b(f,1<b.length?h:h())},!0);return this},off:function(a,b){g(e,a,b);return this}}}}());

View file

@ -1,67 +0,0 @@
// Main cryptalk module
define({
compiles: ['$'],
requires: ['castrato','console','host','client']
}, function ($, requires, data) {
// Require shortcut
var mediator = requires.castrato;
// Route mediator messages
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
.emit('command:connect', undefined, function() {
// Join room and set key if a hash in the format #Room:Key has been provided
if ((hash = window.location.hash)) {
parts = hash.slice(1).split(':');
if (parts[0]) {
mediator.emit('command:join', parts[0]);
}
if (parts[1]) {
mediator.emit('command:key', parts[1]);
}
}
});
});

View file

@ -1,17 +0,0 @@
define(['castrato'], function (castrato) {
var exports = {};
castrato.on('*', function (data, done, name) {
if (name !== 'console:post' && name !== 'notification:send') {
castrato.emit('console:post', {
type: 'server',
data: name + (data ? '(' + JSON.stringify(data) + ')' : ''),
debug: 1
});
}
done();
});
return exports;
});

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,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,42 +0,0 @@
define({
title: "Cryptalk - Online",
ttl: 600000,
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>',
nick: {
maxLen: 20,
minLen: 2,
},
key: {
maxLen: 1024,
minLen: 8,
},
room: {
minLen: 1,
maxLen: 64
},
notifications: {
maxOnePerMs: 3000
}
});

View file

@ -1,22 +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||w.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 x[l]||(x[l]=
[]),x[l].unshift(s(p-1));h&&e.length<b.length&&(m=setTimeout(function(){for(var c,d,l=0,f=0;c=e[l++];)for(f=0;d=b[f++];)c===d&&(b.splice(--f,1),e.splice(--l,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={},w={deepCopy:!0,baseUrl:"",namespace:"default",timeout:1E3,paths:{},shim:{}},k={},E={a:1},x={},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),l=d.length;this.context=b||document;this.selector=a;for(this.length=l;c<l;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=x[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=w.deepCopy);f=n[h]=k.merge(b,{},w,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},b=/(\.[^\\]+)$/;return function(c){var e=c.toLowerCase(),d=e[0]||e.slice(0,1);"/"===d||"\\"===d||"h"===d&&(a[e.slice(0,7)]||a[e.slice(0,8)])||"f"===d&&a[e.slice(0,7)]||(c=w.baseUrl+c);b.test(c)||(c+=".js");return c}}(),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,g){var h=[],x=[],t={isAMD:!1,paths:[],names:[],loaded:[],error:null,loadListener:null,errorListener:null},u,v,z,A;!0!==b&&!1!==b&&(g=f,f=d,d=c,c=b,b=!0);t.isAMD=b;t.onError=function(a,b,c){a instanceof Error||(a=Error('Could require "'+
b+'"; an error occurred while trying to load "'+c+'".'));if(f)f(a);else throw a;};for(A=0;u=c[A++];)if(n[u])if(3===n[u].state)x.push(n[u].exports);else{if(2===n[u].state)throw Error("require(): Rejected dependency. Cannot continue.");F(u,null,t.onError);h.push(u)}else b=a(w.paths[u]||u),e[b]||(t.paths.push(b),t.names.push(u),e[b]=2),h.push(u);h.length?(t.paths&&(t.onPartial=function(a,b,c){t.isAMD&&(n[b]||((v=w.shim[b])?(z=k.is(v.exports,"function")?v.exports:function(){return q[v.exports]},y(b,z,
t.onError)):y(E,b,t.onError)),F(b,null,t.onError));g&&g(a,c)},m(t)),t.isAMD&&G(h,function(a){d.apply(null,a)},t.onError)):d&&d.apply(null,x)}}();y("fandango",k);var I=q.fandango,J=q.define,K=q.require,v={fn:k,define:y,require:B,noConflict:function(e){q.define===y&&(q.define=J);q.require===B&&(q.require=K);e&&q.fandango===v&&(q.fandango=I);return v},defaults:function(e){return e?k.merge(w,e):w}};q.define||(q.define=v.define);q.require||(q.require=v.require);q.fandango=v;q.modules=n;q.awaitingDependency=
x;q.awaitingName=z})(this);

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,88 +0,0 @@
#!/usr/bin/env node
var express = require('express'),
app = express(),
server = require('http').createServer(app),
io = require('socket.io')(server),
port = process.env.PORT || 8080;
server.listen(port, function(){
console.log('listening on *:'+port);
});
// Serve /public/* as /
app.use(express.static(__dirname + '/public'));
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 (req) {
if( socket.current_room !== undefined ) {
var clientsList = io.sockets.adapter.rooms[socket.current_room];
socket.emit('message:server', {msg:'person_count', payload: Object.keys(clientsList).length } );
} 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'} );
}
});
});

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