Initial commit

This commit is contained in:
zhongjin 2018-07-21 14:50:16 +08:00
commit 3af4f5f884
24 changed files with 5894 additions and 0 deletions

7
.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
node_modules
.idea
tmp
admin/i18n/flat.txt
admin/i18n/*/flat.txt
package-lock.json
iob_npm.done

9
.npmignore Normal file
View File

@ -0,0 +1,9 @@
gruntfile.js
example
tasks
test
.travis.yml
appveyor.yml
admin/i18n
package-lock.json
iob_npm.done

23
.travis.yml Normal file
View File

@ -0,0 +1,23 @@
os:
- linux
- osx
language: node_js
node_js:
- '4'
- '6'
- '8'
- '10'
before_script:
- export NPMVERSION=$(echo "$($(which npm) -v)"|cut -c1)
- 'if [[ $NPMVERSION == 5 ]]; then npm install -g npm@5; fi'
- npm -v
- npm install winston@2.3.1
- 'npm install https://git.spacen.net/yunkong2/yunkong2.js-controller/tarball/master --production'
env:
- CXX=g++-4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014-2018 bluefox <dogafox@gmail.com>
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.

477
README.md Normal file
View File

@ -0,0 +1,477 @@
![Logo](admin/socketio.png)
# yunkong2 socket.io
=================
[![NPM version](http://img.shields.io/npm/v/yunkong2.socketio.svg)](https://www.npmjs.com/package/yunkong2.socketio)
[![Downloads](https://img.shields.io/npm/dm/yunkong2.socketio.svg)](https://www.npmjs.com/package/yunkong2.socketio)
[![NPM](https://nodei.co/npm/yunkong2.socketio.png?downloads=true)](https://nodei.co/npm/yunkong2.socketio/)
This adapter used by some WEB applications and adapters to communicate with yunkong2.
Users can use this adapter to connect their products to yunkong2 via web sockets. Actually this adapter is used by Flot, Rickshaw, Vis and mobile to extract data from yunkong2.
You can find in the example [directory](https://git.spacen.net/yunkong2/yunkong2.socketio/tree/master/example) simple application that uses this interface to show some data.
By using of socket.io interface user should understand the [basics and concept](https://git.spacen.net/yunkong2/yunkong2) of the system.
It is useful to read about the [structure of the objects](https://git.spacen.net/yunkong2/yunkong2/blob/master/doc/SCHEMA.md) too.
## Brief description of concept
### Object
Object is description of data point or group. Group could content other datapoints in this case it called channel. If group consists of other channels in this case it called device.
Object is meta information that describes data point and could content: max/min value, unit, name, default value, type of value, information for adapter for communication (e.g. ip address) and so on.
### State
State is actual value of the data point and presented by javascript object:
```
{
val: VALUE,
ack: ACKNOWLEDGED,
ts: TIMESTAMP, // could be converted into time with "new Date(state.ts)" (In older version of js-controller - "new Date(state.ts * 1000)")
lc: TIMESTAMP of last change,
from: ADAPTER_NAME,
q: QUALITY
}
```
States change itself very frequently in compare to objects. (Normally objects should be changed once by creation and that's all)
### Acknowledgment
Every state has attribute "ack". It shows the direction of command.
- If ack=false, it means some other adapter wants to control (write) this variable, so that command will be executed (e.g. light will be switched on).
- If ack=true, it means that device informs about new value. (e.g. light was switched on manually or motion was detected)
**Example**: we have some home automation adapter (HAA) that has one lamp connected under address *haa.0.lamp1*.
- Lamp can be switched on manually with physical switch or via wifi with he help of HAA.
- If vis wants to switch the lamp on via wifi it should set the new value with ```{value: true, ack: false}```.
- When the lamp is switched on it is normally inform HAA about new state and the value should be immediately overwritten with ```{value: true, ack: true}```.
- If the lamp is switched off manually via physical switch it informs HAA about new state with ```{value: false, ack: true}```.
### Quality
Every data point has attribute **q** - *quality*.
## Usage
It is suggested to use example/conn.js for communication.
After inclusion of conn.js file the global object **servConn** could be used to establish the communication with socketio adapter.
**servConn** object has hollowing methods:
### init
- function (connOptions, connCallbacks, objectsRequired)
**connOptions** - is optional parameter:
```
connOptions = {
name: 'name of the connection', // optional - default 'vis.0', used to distinguish connections in socket-io adapter.
connLink: 'http://localhost:8084', // optional - URL of the socket.io adapter. By default it is same URL where the WEB server is.
socketSession: '' // optional - default 'nokey', and used by authentication
};
```
You can pass these parameters by defining the global variables before call of "init" too:
```
var socketUrl = 'http://localhost:8084'; // is connOptions.connLink
var socketSession = ''; // is connOptions.socketSession
servConn.namespace = 'myapp'; // is connOptions.name
```
**connCallbacks** - object with callbacks:
```
connCallbacks = {
onConnChange: function (isConnected) {}, // optional - called if connection state changed.
onObjectChange: function (id, obj) {}, // optional - called if content of some object is changed, new object created or object was deleted (obj = null)
onUpdate: function (id, state) {}, // optional - called if state of some object is changed, new state for object is created or state was deleted (state = null)
onError: function (error) {} // optional - called if some error occurs
};
```
### setState
- function (pointId, value, callback)
set new value of some data point.
E.g. ```servConn.setState('adapter.0.myvalue', true)``` writes ```{val: true, ack: false}``` into *adapter.0.myvalue*.
- **pointId** - is ID of the state, like *adapter.0.myvalue*,
- **value** - new value of the state, could be simple value (string, number, boolean) or object like ```{val: newValue, ack: false, q: 0}```.
In case if used simple value, "ack" will be set to "false".
- **callback** - ```function (error) {}``` - called when the write of new value into DB is performed (not when the device was controlled).
### getStates
- function (IDs, callback)
get the states of more than one state. This command normally is called after the connection is established to get the actual states of used data points.
- **IDs** - pattern or array with IDs. Could be omitted to get all states. Patterns could have wildcards, like: '*.STATE', 'haa.0.*'
- **callback** - ```function (error, states) {}``` - *states* is object like ```{'id1': 'state1', 'id2': 'state2', ...}```. *stateX* are objects with the structure described [above](#state).
### httpGet
- function (url, callback)
calls this URL from PC, where socketio adapter runs.
- **url** - is address to call.
- **callback** - ```function (data) {}``` - result of the request (html body).
### logError
- function (errorText)
writes error message into controller's log.
### getConfig
- function (callback)
reads controller configuration like language, temperature units, point or comma delimiter in floats, date format.
- **callback** - ```function (err, config) {}``` - config looks like:
```
{
"_id": "system.config",
"type": "config",
"common": {
"name": "System configuration",
"language": "de",
"tempUnit": "°C",
"currency": "€",
"dateFormat": "DD.MM.YYYY",
"isFloatComma": true,
"licenseConfirmed": true,
"activeRepo": "fast-online",
"diag": "extended",
"defaultHistory": ""
}
}
```
### getObject
- function (id, callback)
read specific object from DB. With this function the meta information of some object could be read.
- **id** - id of the state, like "haa.0.light1",
- **callback** - ```function (error, obj)``` - obj looks like:
```
{
"_id": "haa.0.light1",
"type": "state",
"common": {
"def": false,
"type": "boolean",
"read": false,
"write": true,
"role": "switch",
"name": "light in floor"
},
"native": {
"CONTROL": "BUTTON.LONG",
"DEFAULT": false,
"FLAGS": 1,
"ID": "PRESS_LONG",
"MAX": true,
"MIN": false,
"OPERATIONS": 6,
"TAB_ORDER": 1,
"TYPE": "ACTION",
"UNIT": ""
},
"enums": ['enum.rooms.floor'],
"acl": {
"object": 1638,
"state": 1638
}
}
```
### getObjects
- function (callback)
read all objects from DB.
- **callback** - ```function (error, objs)``` - objs looks like: ```{'id1': 'object1', 'id2': 'object2', ...}```
### readDir
- function (dirName, callback)
reads files and directories in specified directory.
Files are stored in DB (or similar) and normally should not be accessed directly. File name consist of path, filename and file extension, like "/mobile.0/data/fileName.txt".
- dirName - name of the directory like */mobile.0/data*
- callback - ```function (error, list)``` - list looks like:
```
[
{
file: 'file1.txt',
stats: {
mode: 33188,
size: 527,
atime: Mon, 10 Oct 2011 23:24:11 GMT,
mtime: Mon, 10 Oct 2011 23:24:11 GMT,
ctime: Mon, 10 Oct 2011 23:24:11 GMT,
birthtime: Mon, 10 Oct 2011 23:24:11 GMT
},
isDir: false,
modifiedAt: timeInMs, // new Date().getTime()
createdAt: timeInMs, // new Date().getTime()
},
{
file: 'main',
stats: {
mode: 33188,
atime: Mon, 10 Oct 2011 23:24:11 GMT,
mtime: Mon, 10 Oct 2011 23:24:11 GMT,
ctime: Mon, 10 Oct 2011 23:24:11 GMT,
birthtime: Mon, 10 Oct 2011 23:24:11 GMT
},
isDir: true,
modifiedAt: timeInMs, // new Date().getTime()
createdAt: timeInMs, // new Date().getTime()
},
...
]
```
### mkdir
- function (dirName, callback)
- **callback** - ```function (error) {}```
### unlink
- function (name, callback)
deletes file or directory. Directory must be empty to be deleted.
- dirName - name of the directory or file like */mobile.0/data*.
- **callback** - ```function (error) {}```
### readFile
- function (filename, callback)
- **callback** - ```function (error, fileData, mimeType)```
### readFile64
- function (filename, callback)
- **callback** - ```function (error, data)``` - data is ```{mime: mimeType, data: base64data}```
### writeFile
- function (filename, data, mode, callback)
- **callback** - ```function (error) {}```
### writeFile64
- function (filename, data, mode, callback)
- **callback** - ```function (error) {}```
### renameFile
- function (oldName, newName, callback)
- **callback** - ```function (error) {}```
### getHistory
- function (instance, options, callback)
- **callback** - ```function (error, data, step, sessionId) {}```
### requireLog
- function (isRequire, callback)
activates/deactivates log receiving for this socket.
- **callback** - ```function (error) {}```
### authEnabled
- function ()
reads if the authentication is enabled and which user is logged in
- **callback** - ```function (authEnabled, currentUser) {}```
If authentication is enabled, so current logged in user will be returned, if auth is disabled, so the default user "running as" will be returned.
## Tuning Web-Sockets
On some web-sockets clients there is performance problem with communication. Sometimes this problem is due to fallback of socket.io communication on long polling mechanism.
You can set option *Force Web-Sockets* to force using only web-sockets transport.
## Changelog
### 2.1.1 (2018-06-09)
* (bluefox) Used socket.io Version 1.7.2
* (bluefox) Fix authentication problem
### 2.1.0 (2018-05-04)
* (bluefox) Used socket.io Version 1.7.4
### 2.0.1 (2018-02-28)
* (bluefox) Dropped support of old browsers. Please do not update if you have iPad 1 and so on.
### 1.9.0 (2018-01-14)
* (bluefox) Ready for admin3
### 1.8.7 (2017-11-29)
* (bluefox) Tune cloud work
### 1.8.5 (2017-10-22)
* (bluefox) Escape [] in subscriptions
### 1.8.4 (2017-10-16)
* (bluefox) Check callback validity
### 1.8.3 (2017-10-09)
* (bluefox) Allow authentication via URL
### 1.8.2 (2017-09-20)
* (bluefox) Fix cmdExec command
### 1.8.1 (2017-09-13)
* (bluefox) Fix user access rights for sendToHost
### 1.8.0 (2017-08-06)
* (bluefox) Support the access to admin via yunkong2.pro
### 1.7.5 (2017-05-24)
* (bluefox) fix error if subscribe is empty
### 1.7.4 (2017-01-04)
* (bluefox) fix error with authentication
### 1.7.3 (2016-11-13)
* (bluefox) support of socket extensions
### 1.7.2 (2016-11-06)
* (bluefox) Fix unsubscribe of states
### 1.7.1 (2016-10-11)
* (bluefox) Fix authentication for app
### 1.7.0 (2016-08-30)
* (bluefox) сompatible only with new admin
### 1.6.1 (2016-08-29)
* (bluefox) fix error by checking user name
### 1.6.0 (2016-08-27)
* (bluefox) support of letsencrypt certificates
### 1.5.4 (2016-08-26)
* (bluefox) fix error in socket.js
### 1.5.3 (2016-08-14)
* (bluefox) support of force only web sockets transport
### 1.5.2 (2016-07-06)
* (bluefox) support of chained certificates
### 1.5.1 (2016-06-28)
* (bluefox) add sendToHost command
### 1.5.0 (2016-06-17)
* (bluefox) preparations for cloud
### 1.4.1 (2016-05-13)
* (bluefox) change getHistory function
### 1.4.0 (2016-04-24)
* (bluefox) encode json files
### 1.3.0 (2016-03-17)
* (bluefox) rename files
### 1.2.3 (2015-12-24)
* (bluefox) support of authentication over URL
### 1.2.2 (2015-12-09)
* (bluefox) remove unused parameter "cache"
### 1.2.0 (2015-11-15)
* (bluefox) add version compatibility check
### 1.1.0 (2015-11-14)
* (Smiling_Jack) add getHistory
### 1.0.0 (2015-09-30)
* (bluefox) stop adapter before update
### 0.4.5 (2015-08-11)
* (bluefox) update packets
### 0.4.4 (2015-07-07)
* (bluefox) extend writeFile with mode
### 0.4.3 (2015-07-06)
* (bluefox) add chmodFile
### 0.4.1 (2015-06-13)
* (bluefox) add default ttl
* (bluefox) enable run from "web" and add permissions check
### 0.4.0 (2015-06-13)
* (bluefox) add permissions support
### 0.3.1 (2015-05-19)
* (bluefox) support of subscribe on objectChanged
### 0.3.0 (2015-04-23)
* (bluefox) enable security
### 0.2.3 (2015-03-07)
* (bluefox) extend getStates to support list of objects
### 0.2.2 (2015-02-14)
* (bluefox) fix error with objectChanged event
### 0.2.0 (2015-01-16)
* (bluefox) make socket usable as module
### 0.1.6 (2015-01-08)
* (bluefox) support of subscribe for different sockets. Support of socket names. Diagnostic info in socket.0.connected
### 0.1.5 (2015-01-07)
* (bluefox) fix error with update of states and objects
### 0.1.4 (2015-01-06)
* (bluefox) support of file manager in vis
### 0.1.3 (2015-01-02)
* (bluefox) enable adapter by default
### 0.1.2 (2015-01-02)
* (bluefox) add "request" module to package.json
### 0.1.1 (2015-01-02)
* (bluefox) enable npm install
### 0.1.0 (2014-12-28)
* (bluefox) support of read/write files
### 0.0.5 (2014-12-19)
* (bluefox) support of setObjects command
### 0.0.4 (2014-12-10)
* (bluefox) support of https sockets
### 0.0.3 (2014-12-05)
* (bluefox) support of https sockets
### 0.0.2 (2014-11-24)
* (bluefox) fix error by start
### 0.0.1 (2014-10-10)
* (bluefox) authentication works
## License
The MIT License (MIT)
Copyright (c) 2014-2018 bluefox <dogafox@gmail.com>

159
admin/index.html Normal file
View File

@ -0,0 +1,159 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="../../lib/css/themes/jquery-ui/redmond/jquery-ui.min.css"/>
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
<script type="text/javascript" src="../../lib/js/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../lib/js/jquery-ui-1.10.3.full.min.js"></script>
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script>
<style>
.number {
width: 70px
}
</style>
<script type="text/javascript">
function showHideSettings() {
var $secure = $('#secure');
var $auth = $('#auth');
if ($secure.prop('checked')) {
$('#_certPublic').show();
$('#_certPrivate').show();
$('#_certChained').show();
$('.le-settings').show();
if ($('#leEnabled').prop('checked')) {
$('.le-sub-settings').show();
if ($('#leUpdate').prop('checked')) {
$('.le-sub-settings-update').show();
} else {
$('.le-sub-settings-update').hide();
}
} else {
$('.le-sub-settings').hide();
}
} else {
$('#_certPublic').hide();
$('#_certPrivate').hide();
$('#_certChained').hide();
$auth.prop('checked', false);
$('.le-settings').hide();
}
if ($auth.prop('checked')) {
$secure.prop('checked', true);
$('#defaultUser').val('admin');
$('.defaultUser').hide();
} else {
$('.defaultUser').show();
}
}
// the function loadSettings has to exist ...
function load(settings, onChange) {
if (!settings) return;
getIPs(function(ips) {
for (var i = 0; i < ips.length; i++) {
$('#bind').append('<option value="' + ips[i].address + '">' + ips[i].name + '</option>');
}
$('#bind.value').val(settings.bind);
});
if (!settings.lePort) settings.lePort = 80;
$('.value').each(function () {
var key = $(this).attr('id');
var $value = $('#' + key + '.value');
if ($value.attr('type') === 'checkbox') {
$value.prop('checked', settings[key]).change(function() {
onChange();
});
} else {
$value.val(settings[key]).change(function() {
onChange();
}).keyup(function() {
onChange();
});
}
});
onChange(false);
fillSelectCertificates('#certPublic', 'public', settings.certPublic);
fillSelectCertificates('#certPrivate', 'private', settings.certPrivate);
fillSelectCertificates('#certChained', 'chained', settings.certChained);
fillUsers('#defaultUser', settings.defaultUser);
$('#auth').change(function () {
if ($(this).prop('checked')) {
$('#secure').prop('checked', true);
}
showHideSettings();
});
$('#secure').change(function () {
showHideSettings();
});
showHideSettings();
}
function save(callback) {
var obj = {};
$('.value').each(function () {
var $this = $(this);
if ($this.attr('type') === 'checkbox') {
obj[$this.attr('id')] = $this.prop('checked');
} else {
obj[$this.attr('id')] = $this.val();
}
});
if ($('#secure').prop('checked') && (!$('#certPrivate').val() || !$('#certPublic').val())) {
showMessage(_('Set certificates or load it first in the system settings (right top).'));
return;
}
callback(obj);
}
</script>
</head>
<body>
<!-- you have to put your config page in a div with id adapter-container -->
<div id="adapter-container">
<table><tr><td><img src="socketio.png"></td><td><h3 class="translate">socket.io adapter settings</h3></td></tr></table>
<table>
<tr><td><label class="translate" for="bind">IP:</label></td><td><select class="value" id="bind"></select></td></tr>
<tr><td><label class="translate" for="port">Port:</label></td><td><input class="value" id="port" size="5" maxlength="5"/></td></tr>
<tr><td><label class="translate" for="secure">Secure(HTTPS):</label></td><td><input class="value" id="secure" type="checkbox" /></td></tr>
<tr><td><label class="translate" for="auth">Authentication:</label></td><td><input class="value" id="auth" type="checkbox" /></td></tr>
<tr id="_certPublic">
<td class="translate"><label class="translate" for="certPublic">Public certificate:</label></td>
<td><select id="certPublic" class="value"></select></td>
</tr>
<tr id="_certPrivate">
<td class="translate"><label class="translate" for="certPrivate">Private certificate:</label></td>
<td><select id="certPrivate" class="value"></select></td>
</tr>
<tr id="_certChained">
<td class="translate"><label class="translate" for="certChained">Chained certificate:</label></td>
<td><select id="certChained" class="value"></select></td>
</tr>
<tr class="defaultUser"><td class="translate"><label class="translate" for="defaultUser">Run as:</label></td><td><select class="value" id="defaultUser"></select></td></tr>
<tr class="socketio"><td class="translate"><label class="translate" for="forceWebSockets">Force Web-Sockets:</label></td><td><input type="checkbox" class="value" id="forceWebSockets"/></td></tr>
<tr><td colspan="2">&nbsp;</td></tr>
<tr class="le-settings"><td colspan="2"><h3 class="translate">Let's Encrypt settings</h3></tr>
<tr class="le-settings"><td><label for="leEnabled" class="translate">Use Lets Encrypt certificates:</label></td><td><input class="value" id="leEnabled" type="checkbox" /></td></tr>
<tr class="le-settings le-sub-settings"><td><label for="leUpdate" class="translate">Use this instance for automatic update:</label></td><td><input class="value" id="leUpdate" type="checkbox" /></td></tr>
<tr class="le-settings le-sub-settings le-sub-settings-update"><td><label for="lePort" class="translate">Port to check the domain:</label></td><td><input class="value number" id="lePort" type="number" size="5" maxlength="5" /></td></tr>
</table>
</div>
</body>
</html>

208
admin/index_m.html Normal file
View File

@ -0,0 +1,208 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="../../css/adapter.css"/>
<link rel="stylesheet" type="text/css" href="../../lib/css/materialize.css">
<script type="text/javascript" src="../../lib/js/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="../../socket.io/socket.io.js"></script>
<script type="text/javascript" src="../../js/translate.js"></script>
<script type="text/javascript" src="../../lib/js/materialize.js"></script>
<script type="text/javascript" src="../../js/adapter-settings.js"></script>
<script type="text/javascript" src="words.js"></script>
<script type="text/javascript">
function showHideSettings() {
var $secure = $('#secure');
var $auth = $('#auth');
if ($secure.prop('checked')) {
$('.col-certPublic').show();
$('.col-certPrivate').show();
$('.col-certChained').show();
$('.le-settings').removeClass('disabled');
$auth.removeClass('disabled');
if ($('#leEnabled').prop('checked')) {
$('.le-sub-settings').show();
if ($('#leUpdate').prop('checked')) {
$('.le-sub-settings-update').show();
} else {
$('.le-sub-settings-update').hide();
}
} else {
$('.le-sub-settings').hide();
}
} else {
$('.col-certPublic').hide();
$('.col-certPrivate').hide();
$('.col-certChained').hide();
if ($auth.prop('checked')) {
showToast(_('Please activate secure communication'));
$auth.prop('checked', false);
}
$auth.addClass('disabled');
$('.le-settings').addClass('disabled');
}
if ($auth.prop('checked')) {
$secure.prop('checked', true);
$('#defaultUser').val('admin');
$('.col-defaultUser').hide();
$('.col-ttl').show();
} else {
$('.col-defaultUser').show();
$('.col-ttl').hide();
}
}
// the function loadSettings has to exist ...
function load(settings, onChange) {
if (!settings) return;
getIPs(function(ips) {
for (var i = 0; i < ips.length; i++) {
$('#bind').append('<option value="' + ips[i].address + '">' + ips[i].name + '</option>');
}
$('#bind.value').val(settings.bind).select();
});
if (!settings.lePort) settings.lePort = 80;
$('.value').each(function () {
var key = $(this).attr('id');
var $value = $('#' + key + '.value');
if ($value.attr('type') === 'checkbox') {
$value.prop('checked', settings[key]).change(function() {
showHideSettings();
onChange();
});
} else {
$value.val(settings[key]).change(function() {
onChange();
}).keyup(function() {
onChange();
});
}
});
onChange(false);
fillSelectCertificates('#certPublic', 'public', settings.certPublic);
fillSelectCertificates('#certPrivate', 'private', settings.certPrivate);
fillSelectCertificates('#certChained', 'chained', settings.certChained);
fillUsers('#defaultUser', settings.defaultUser);
showHideSettings();
}
function save(callback) {
var obj = {};
$('.value').each(function () {
var $this = $(this);
if ($this.attr('type') === 'checkbox') {
obj[$this.attr('id')] = $this.prop('checked');
} else {
obj[$this.attr('id')] = $this.val();
}
});
if ($('#secure').prop('checked') && (!$('#certPrivate').val() || !$('#certPublic').val())) {
showToast(null, _('Set certificates or load it first in the system settings (right top).'));
return;
}
callback(obj);
}
</script>
</head>
<body>
<!-- you have to put your config page in a div with id adapter-container -->
<div class="m adapter-container">
<div class="row">
<div class="col s12">
<ul class="tabs">
<li class="tab col s2"><a href="#tab-main" class="translate active">Main settings</a></li>
<li class="tab col s2 le-settings"><a href="#tab-le" class="translate">Let's Encrypt SSL</a></li>
</ul>
</div>
<div id="tab-main" class="col s12 page">
<div class="row">
<div class="col s12 m4 l2">
<img src="socketio.png" class="logo">
</div>
</div>
<div class="row">
<div class="col s12 m8 l5">
<select class="value" id="bind"></select>
<label class="translate" for="bind">IP:</label>
</div>
<div class="col s12 m4 l1">
<input class="value number" id="port" min="1" max="65565" type="number"/>
<label class="translate" for="port">Port:</label>
</div>
</div>
<div class="row">
<div class="col s12 m3">
<input class="value" id="secure" type="checkbox" />
<label class="translate" for="secure">Secure(HTTPS):</label>
</div>
<div class="col s12 m3 col-certPublic">
<select id="certPublic" class="value"></select>
<label class="translate" for="certPublic">Public certificate:</label>
</div>
<div class="col s12 m3 col-certPrivate">
<select id="certPrivate" class="value"></select>
<label class="translate" for="certPrivate">Private certificate:</label>
</div>
<div class="col s12 m3 col-certChained">
<select id="certChained" class="value"></select>
<label class="translate" for="certChained">Chained certificate:</label>
</div>
</div>
<div class="row">
<div class="col s12 m3">
<input class="value" id="auth" type="checkbox" />
<label class="translate" for="auth">Authentication:</label>
</div>
<div class="col s12 m3 col-defaultUser">
<select class="value" id="defaultUser"></select>
<label class="translate" for="defaultUser">Run as:</label>
</div>
<div class="col s12 m3 col-ttl">
<input class="value" id="ttl" />
<label class="translate" for="ttl">Login timeout(sec):</label>
</div>
</div>
<div class="row socketio">
<div class="col s12 m3">
<input type="checkbox" class="value" id="forceWebSockets"/>
<span class="translate" for="forceWebSockets">Force Web-Sockets:</span>
</div>
</div>
</div>
<div id="tab-le" class="col s12 page">
<div class="row">
<div class="col s12">
<img src="../../img/le.png" class="logo-le">
</div>
</div>
<div class="row">
<div class="col s11">
<input class="value" id="leEnabled" type="checkbox" data-link="https://git.spacen.net/yunkong2/yunkong2.admin#lets-encrypt-certificates"/>
<label for="leEnabled" class="translate">Use Lets Encrypt certificates:</label>
</div>
</div>
<div class="row le-sub-settings">
<div class="col s11">
<input class="value" id="leUpdate" type="checkbox" data-link="https://git.spacen.net/yunkong2/yunkong2.admin#lets-encrypt-certificates"/>
<label for="leUpdate" class="translate">Use this instance for automatic update:</label>
</div>
</div>
<div class="row le-sub-settings le-sub-settings-update">
<div class="col s11 m4 l2">
<input class="value number" id="lePort" type="number" size="5" maxlength="5" data-link="https://git.spacen.net/yunkong2/yunkong2.admin#lets-encrypt-certificates"/>
<label for="lePort" class="translate">Port to check the domain:</label>
</div>
</div>
</div>
</div>
</div>
</body>
</html>

BIN
admin/socketio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

22
admin/words.js Normal file
View File

@ -0,0 +1,22 @@
// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n
/*global systemDictionary:true */
'use strict';
systemDictionary = {
"socket.io adapter settings": { "en": "socket.io adapter settings", "de": "socket.io adapter settings", "ru": "socket.io adapter settings", "pt": "Configurações do adaptador socket.io", "nl": "socket.io-adapterinstellingen", "fr": "Paramètres de l'adaptateur socket.io", "it": "impostazioni della scheda socket.io", "es": "Configuración del adaptador socket.io", "pl": "ustawienia adaptera socket.io"},
"Run as:": { "en": "Run as", "de": "Laufen unter Anwender", "ru": "Запустить от пользователя", "pt": "Correr como", "nl": "Rennen als", "fr": "Courir comme", "it": "Correre come", "es": "Correr como", "pl": "Uruchom jako"},
"IP:": { "en": "IP", "de": "IP", "ru": "IP", "pt": "IP", "nl": "IK P", "fr": "IP", "it": "IP", "es": "IP", "pl": "IP"},
"Port:": { "en": "Port", "de": "Port", "ru": "Порт", "pt": "Porta", "nl": "Haven", "fr": "Port", "it": "Porta", "es": "Puerto", "pl": "Port"},
"Secure(HTTPS):": { "en": "Secure(HTTPS)", "de": "Verschlüsselung(HTTPS)", "ru": "Шифрование(HTTPS)", "pt": "Seguro (HTTPS)", "nl": "Secure (HTTPS)", "fr": "Sécurisé (HTTPS)", "it": "Sicuro (HTTPS)", "es": "Seguro (HTTPS)", "pl": "Bezpieczne (HTTPS)"},
"Authentication:": { "en": "Authentication", "de": "Authentifikation", "ru": "Аутентификация", "pt": "Autenticação", "nl": "authenticatie", "fr": "Authentification", "it": "Autenticazione", "es": "Autenticación", "pl": "Poświadczenie"},
"Listen on all IPs": { "en": "Listen on all IPs", "de": "An allen IP Adressen hören", "ru": "Открыть сокет на всех IP адресах", "pt": "Ouça todos os IPs", "nl": "Luister op alle IP's", "fr": "Écoutez sur toutes les adresses IP", "it": "Ascolta su tutti gli IP", "es": "Escuchar en todas las direcciones IP", "pl": "Posłuchaj na wszystkich IP"},
"help_tip": { "en": "On save the adapter restarts with new configuration immediately", "de": "Beim Speichern von Einstellungen der Adapter wird sofort neu gestartet.", "ru": "Сразу после сохранения настроек драйвер перезапуститься с новыми значениями", "pt": "Em salvar, o adaptador reinicia com a nova configuração imediatamente", "nl": "Bij opslaan wordt de adapter onmiddellijk opnieuw opgestart met een nieuwe configuratie", "fr": "Sur enregistrer l'adaptateur redémarre avec la nouvelle configuration immédiatement", "it": "Al salvataggio, l'adattatore si riavvia immediatamente con la nuova configurazione", "es": "Al guardar, el adaptador se reinicia con una nueva configuración de inmediato", "pl": "Po zapisaniu adapter natychmiast uruchamia się ponownie z nową konfiguracją"},
"Public certificate:": { "en": "Public certificate", "de": "Publikzertifikat", "ru": "'Public' сертификат", "pt": "Certificado público", "nl": "Openbaar certificaat", "fr": "Certificat public", "it": "Certificato pubblico", "es": "Certificado público", "pl": "Certyfikat publiczny"},
"Private certificate:": { "en": "Private certificate", "de": "Privatzertifikat", "ru": "'Private' сертификат", "pt": "Certificado privado", "nl": "Privé certificaat", "fr": "Certificat privé", "it": "Certificato privato", "es": "Certificado privado", "pl": "Prywatny certyfikat"},
"Chained certificate:": { "en": "Chained certificate", "de": "Kettenzertifikat", "ru": "'Chained' сертификат", "pt": "Certificado acorrentado", "nl": "Geketend certificaat", "fr": "Certificat chaîné", "it": "Certificato incatenato", "es": "Certificado encadenado", "pl": "Przykuty certyfikat"},
"Force Web-Sockets:": { "en": "Force Web-Sockets", "de": "Nur Web-Sockets", "ru": "Только Web-Sockets", "pt": "Forçar Web-Sockets", "nl": "Force Web-Sockets", "fr": "Forcer les Web-Sockets", "it": "Force Web-Sockets", "es": "Force Web-Sockets", "pl": "Wymuszaj gniazda internetowe"},
"Let's Encrypt settings": { "en": "Let's Encrypt settings", "de": "Einstellungen Let's Encrypt", "ru": "Настройки Let's Encrypt", "pt": "Vamos criptografar configurações", "nl": "Laten we de instellingen versleutelen", "fr": "Cryptons les paramètres", "it": "Let's Encrypt settings", "es": "Vamos a cifrar la configuración", "pl": "Zakodujmy ustawienia"},
"Use Lets Encrypt certificates:": { "en": "Use Let's Encrypt certificates", "de": "Benutzen Let's Encrypt Zertifikate", "ru": "Использовать сертификаты Let's Encrypt", "pt": "Use Vamos criptografar certificados", "nl": "Gebruik Let's Encrypt-certificaten", "fr": "Utiliser les certificats Let's Encrypt", "it": "Utilizza Let's Encrypt certificates", "es": "Utilice los certificados Let's Encrypt", "pl": "Użyj Let's Encrypt certificates"},
"Use this instance for automatic update:": { "en": "Use this instance for automatic update", "de": "Benutze diese Instanz für automatische Updates", "ru": "Обновлять сертификаты в этом драйвере", "pt": "Use esta instância para atualização automática", "nl": "Gebruik deze instantie voor automatische update", "fr": "Utilisez cette instance pour la mise à jour automatique", "it": "Utilizza questa istanza per l'aggiornamento automatico", "es": "Use esta instancia para la actualización automática", "pl": "Użyj tej instancji do automatycznej aktualizacji"},
"Port to check the domain:": { "en": "Port to check the domain", "de": "Port um die Domain zu prüfen", "ru": "Порт для проверки доменного имени", "pt": "Porta para verificar o domínio", "nl": "Poort om het domein te controleren", "fr": "Port pour vérifier le domaine", "it": "Porta per controllare il dominio", "es": "Puerto para verificar el dominio", "pl": "Port do sprawdzenia domeny"}
};

25
appveyor.yml Normal file
View File

@ -0,0 +1,25 @@
version: 'test-{build}'
environment:
matrix:
- nodejs_version: '4'
- nodejs_version: '6'
- nodejs_version: '8'
- nodejs_version: '10'
platform:
- x86
- x64
clone_folder: 'c:\projects\%APPVEYOR_PROJECT_NAME%'
install:
- ps: 'Install-Product node $env:nodejs_version $env:platform'
- ps: '$NpmVersion = (npm -v).Substring(0,1)'
- ps: 'if($NpmVersion -eq 5) { npm install -g npm@5 }'
- ps: npm --version
- npm install
- npm install winston@2.3.1
- 'npm install https://git.spacen.net/yunkong2/yunkong2.js-controller/tarball/master --production'
test_script:
- echo %cd%
- node --version
- npm --version
- npm test
build: 'off'

1262
example/conn.js Normal file

File diff suppressed because it is too large Load Diff

53
example/index.html Normal file
View File

@ -0,0 +1,53 @@
<!doctype html>
<html>
<head>
<!-- Replace "localhost" with real IP address of controller, if default port changed, correct it too -->
<script type="text/javascript" src="http://localhost:8084/socket.io/socket.io.js"></script>
<script type="text/javascript" src="conn.js"></script>
</head>
<body>
<!--
Check the browser console!
-->
<script type="text/javascript">
servConn.namespace = 'mobile.0';
servConn._useStorage = false;
var states = [];
servConn.init({
name: 'mobile.0', // optional - default 'vis.0'
connLink: 'http://localhost:8084', // optional URL of the socket.io adapter
socketSession: '' // optional - used by authentication
}, {
onConnChange: function (isConnected) {
if (isConnected) {
console.log('connected');
servConn.getStates(function (err, _states) {
var count = 0;
for (var id in _states) {
count++;
}
console.log('Received ' + count + ' states.');
states = _states;
});
} else {
console.log('disconnected');
}
},
onRefresh: function () {
window.location.reload();
},
onUpdate: function (id, state) {
setTimeout(function () {
console.log('NEW VALUE of ' + id + ': ' + JSON.stringify(state));
states[id] = state;
}, 0);
},
onError: function (err) {
window.alert(_('Cannot execute %s for %s, because of insufficient permissions', err.command, err.arg), _('Insufficient permissions'), 'alert', 600);
}
});
</script>
</body>

401
gulpfile.js Normal file
View File

@ -0,0 +1,401 @@
'use strict';
var gulp = require('gulp');
var fs = require('fs');
var pkg = require('./package.json');
var iopackage = require('./io-package.json');
var version = (pkg && pkg.version) ? pkg.version : iopackage.common.version;
/*var appName = getAppName();
function getAppName() {
var parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 1].split('.')[0].toLowerCase();
}
*/
const fileName = 'words.js';
var languages = {
en: {},
de: {},
ru: {},
pt: {},
nl: {},
fr: {},
it: {},
es: {},
pl: {}
};
function lang2data(lang, isFlat) {
var str = isFlat ? '' : '{\n';
var count = 0;
for (var w in lang) {
if (lang.hasOwnProperty(w)) {
count++;
if (isFlat) {
str += (lang[w] === '' ? (isFlat[w] || w) : lang[w]) + '\n';
} else {
var key = ' "' + w.replace(/"/g, '\\"') + '": ';
str += key + '"' + lang[w].replace(/"/g, '\\"') + '",\n';
}
}
}
if (!count) return isFlat ? '' : '{\n}';
if (isFlat) {
return str;
} else {
return str.substring(0, str.length - 2) + '\n}';
}
}
function readWordJs(src) {
try {
var words;
if (fs.existsSync(src + 'js/' + fileName)) {
words = fs.readFileSync(src + 'js/' + fileName).toString();
} else {
words = fs.readFileSync(src + fileName).toString();
}
var lines = words.split(/\r\n|\r|\n/g);
var i = 0;
while (!lines[i].match(/^systemDictionary = {/)) {
i++;
}
lines.splice(0, i);
// remove last empty lines
i = lines.length - 1;
while (!lines[i]) {
i--;
}
if (i < lines.length - 1) {
lines.splice(i + 1);
}
lines[0] = lines[0].replace('systemDictionary = ', '');
lines[lines.length - 1] = lines[lines.length - 1].trim().replace(/};$/, '}');
words = lines.join('\n');
var resultFunc = new Function('return ' + words + ';');
return resultFunc();
} catch (e) {
return null;
}
}
function padRight(text, totalLength) {
return text + (text.length < totalLength ? new Array(totalLength - text.length).join(' ') : '');
}
function writeWordJs(data, src) {
var text = '// DO NOT EDIT THIS FILE!!! IT WILL BE AUTOMATICALLY GENERATED FROM src/i18n\n';
text += '/*global systemDictionary:true */\n';
text += '\'use strict\';\n\n';
text += 'systemDictionary = {\n';
for (var word in data) {
if (data.hasOwnProperty(word)) {
text += ' ' + padRight('"' + word.replace(/"/g, '\\"') + '": {', 50);
var line = '';
for (var lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
line += '"' + lang + '": "' + padRight(data[word][lang].replace(/"/g, '\\"') + '",', 50) + ' ';
}
}
if (line) {
line = line.trim();
line = line.substring(0, line.length - 1);
}
text += line + '},\n';
}
}
text = text.replace(/},\n$/, '}\n');
text += '};';
if (fs.existsSync(src + 'js/' + fileName)) {
fs.writeFileSync(src + 'js/' + fileName, text);
} else {
fs.writeFileSync(src + '' + fileName, text);
}
}
const EMPTY = '';
function words2languages(src) {
var langs = Object.assign({}, languages);
var data = readWordJs(src);
if (data) {
for (var word in data) {
if (data.hasOwnProperty(word)) {
for (var lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
langs[lang][word] = data[word][lang];
// pre-fill all other languages
for (var j in langs) {
if (langs.hasOwnProperty(j)) {
langs[j][word] = langs[j][word] || EMPTY;
}
}
}
}
}
}
if (!fs.existsSync(src + 'i18n/')) {
fs.mkdirSync(src + 'i18n/');
}
for (var l in langs) {
if (!langs.hasOwnProperty(l)) continue;
var keys = Object.keys(langs[l]);
//keys.sort();
var obj = {};
for (var k = 0; k < keys.length; k++) {
obj[keys[k]] = langs[l][keys[k]];
}
if (!fs.existsSync(src + 'i18n/' + l)) {
fs.mkdirSync(src + 'i18n/' + l);
}
fs.writeFileSync(src + 'i18n/' + l + '/translations.json', lang2data(obj));
}
} else {
console.error('Cannot read or parse ' + fileName);
}
}
function words2languagesFlat(src) {
var langs = Object.assign({}, languages);
var data = readWordJs(src);
if (data) {
for (var word in data) {
if (data.hasOwnProperty(word)) {
for (var lang in data[word]) {
if (data[word].hasOwnProperty(lang)) {
langs[lang][word] = data[word][lang];
// pre-fill all other languages
for (var j in langs) {
if (langs.hasOwnProperty(j)) {
langs[j][word] = langs[j][word] || EMPTY;
}
}
}
}
}
}
var keys = Object.keys(langs.en);
//keys.sort();
for (var l in langs) {
if (!langs.hasOwnProperty(l)) continue;
var obj = {};
for (var k = 0; k < keys.length; k++) {
obj[keys[k]] = langs[l][keys[k]];
}
langs[l] = obj;
}
if (!fs.existsSync(src + 'i18n/')) {
fs.mkdirSync(src + 'i18n/');
}
for (var ll in langs) {
if (!langs.hasOwnProperty(ll)) continue;
if (!fs.existsSync(src + 'i18n/' + ll)) {
fs.mkdirSync(src + 'i18n/' + ll);
}
fs.writeFileSync(src + 'i18n/' + ll + '/flat.txt', lang2data(langs[ll], langs.en));
}
fs.writeFileSync(src + 'i18n/flat.txt', keys.join('\n'));
} else {
console.error('Cannot read or parse ' + fileName);
}
}
function languagesFlat2words(src) {
var dirs = fs.readdirSync(src + 'i18n/');
var langs = {};
var bigOne = {};
var order = Object.keys(languages);
dirs.sort(function (a, b) {
var posA = order.indexOf(a);
var posB = order.indexOf(b);
if (posA === -1 && posB === -1) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
} else if (posA === -1) {
return -1;
} else if (posB === -1) {
return 1;
} else {
if (posA > posB) return 1;
if (posA < posB) return -1;
return 0;
}
});
var keys = fs.readFileSync(src + 'i18n/flat.txt').toString().split('\n');
for (var l = 0; l < dirs.length; l++) {
if (dirs[l] === 'flat.txt') continue;
var lang = dirs[l];
var values = fs.readFileSync(src + 'i18n/' + lang + '/flat.txt').toString().split('\n');
langs[lang] = {};
keys.forEach(function (word, i) {
langs[lang][word] = values[i].replace(/<\/ i>/g, '</i>').replace(/<\/ b>/g, '</b>').replace(/<\/ span>/g, '</span>').replace(/% s/g, ' %s');
});
var words = langs[lang];
for (var word in words) {
if (words.hasOwnProperty(word)) {
bigOne[word] = bigOne[word] || {};
if (words[word] !== EMPTY) {
bigOne[word][lang] = words[word];
}
}
}
}
// read actual words.js
var aWords = readWordJs();
var temporaryIgnore = ['pt', 'fr', 'nl', 'flat.txt'];
if (aWords) {
// Merge words together
for (var w in aWords) {
if (aWords.hasOwnProperty(w)) {
if (!bigOne[w]) {
console.warn('Take from actual words.js: ' + w);
bigOne[w] = aWords[w]
}
dirs.forEach(function (lang) {
if (temporaryIgnore.indexOf(lang) !== -1) return;
if (!bigOne[w][lang]) {
console.warn('Missing "' + lang + '": ' + w);
}
});
}
}
}
writeWordJs(bigOne, src);
}
function languages2words(src) {
var dirs = fs.readdirSync(src + 'i18n/');
var langs = {};
var bigOne = {};
var order = Object.keys(languages);
dirs.sort(function (a, b) {
var posA = order.indexOf(a);
var posB = order.indexOf(b);
if (posA === -1 && posB === -1) {
if (a > b) return 1;
if (a < b) return -1;
return 0;
} else if (posA === -1) {
return -1;
} else if (posB === -1) {
return 1;
} else {
if (posA > posB) return 1;
if (posA < posB) return -1;
return 0;
}
});
for (var l = 0; l < dirs.length; l++) {
if (dirs[l] === 'flat.txt') continue;
var lang = dirs[l];
langs[lang] = fs.readFileSync(src + 'i18n/' + lang + '/translations.json').toString();
langs[lang] = JSON.parse(langs[lang]);
var words = langs[lang];
for (var word in words) {
if (words.hasOwnProperty(word)) {
bigOne[word] = bigOne[word] || {};
if (words[word] !== EMPTY) {
bigOne[word][lang] = words[word];
}
}
}
}
// read actual words.js
var aWords = readWordJs();
var temporaryIgnore = ['pt', 'fr', 'nl', 'it'];
if (aWords) {
// Merge words together
for (var w in aWords) {
if (aWords.hasOwnProperty(w)) {
if (!bigOne[w]) {
console.warn('Take from actual words.js: ' + w);
bigOne[w] = aWords[w]
}
dirs.forEach(function (lang) {
if (temporaryIgnore.indexOf(lang) !== -1) return;
if (!bigOne[w][lang]) {
console.warn('Missing "' + lang + '": ' + w);
}
});
}
}
}
writeWordJs(bigOne, src);
}
gulp.task('adminWords2languages', function (done) {
words2languages('./admin/');
done();
});
gulp.task('adminWords2languagesFlat', function (done) {
words2languagesFlat('./admin/');
done();
});
gulp.task('adminLanguagesFlat2words', function (done) {
languagesFlat2words('./admin/');
done();
});
gulp.task('adminLanguages2words', function (done) {
languages2words('./admin/');
done();
});
gulp.task('updatePackages', function (done) {
iopackage.common.version = pkg.version;
iopackage.common.news = iopackage.common.news || {};
if (!iopackage.common.news[pkg.version]) {
var news = iopackage.common.news;
var newNews = {};
newNews[pkg.version] = {
en: 'news',
de: 'neues',
ru: 'новое'
};
iopackage.common.news = Object.assign(newNews, news);
}
fs.writeFileSync('io-package.json', JSON.stringify(iopackage, null, 4));
done();
});
gulp.task('updateReadme', function (done) {
var readme = fs.readFileSync('README.md').toString();
var pos = readme.indexOf('## Changelog\n');
if (pos !== -1) {
var readmeStart = readme.substring(0, pos + '## Changelog\n'.length);
var readmeEnd = readme.substring(pos + '## Changelog\n'.length);
if (readme.indexOf(version) === -1) {
var timestamp = new Date();
var date = timestamp.getFullYear() + '-' +
('0' + (timestamp.getMonth() + 1).toString(10)).slice(-2) + '-' +
('0' + (timestamp.getDate()).toString(10)).slice(-2);
var news = '';
if (iopackage.common.news && iopackage.common.news[pkg.version]) {
news += '* ' + iopackage.common.news[pkg.version].en;
}
fs.writeFileSync('README.md', readmeStart + '### ' + version + ' (' + date + ')\n' + (news ? news + '\n\n' : '\n') + readmeEnd);
}
}
done();
});
gulp.task('default', ['updatePackages', 'updateReadme']);

204
io-package.json Normal file
View File

@ -0,0 +1,204 @@
{
"common": {
"name": "socketio",
"version": "2.1.1",
"title": "socket.io",
"desc": {
"en": "This adapter allows to communicate different web applications with yunkong2",
"de": "Dieser Adapter ermöglicht die Kommunikation verschiedener Web-Anwendungen mit yunkong2",
"ru": "Этот адаптер позволяет соединяться различным веб-приложениям с yunkong2",
"pt": "Este adaptador permite comunicar diferentes aplicativos da web com yunkong2",
"nl": "Met deze adapter kunt u verschillende webtoepassingen communiceren met yunkong2",
"fr": "Cet adaptateur permet de communiquer différentes applications web avec yunkong2",
"it": "Questo adattatore consente di comunicare diverse applicazioni Web con yunkong2",
"es": "Este adaptador permite comunicar diferentes aplicaciones web con yunkong2",
"pl": "Ten adapter umożliwia komunikację z różnymi aplikacjami WWW za pomocą yunkong2"
},
"news": {
"2.1.1": {
"en": "Used socket.io Version 1.7.5\nAuthentication problem was fixed",
"de": "Gebrauchte socket.io Version 1.7.5\nAuthentifizierungsproblem wurde behoben",
"ru": "Используемая версия socket.io 1.7.5\nИсправлена ​​проблема аутентификации",
"pt": "Usado socket.io versão 1.7.5\nProblema de autenticação foi corrigido",
"nl": "Gebruikte socket.io versie 1.7.5\nVerificatieprobleem is opgelost",
"fr": "Version socket.io utilisée 1.7.5\nLe problème d'authentification a été corrigé",
"it": "Usato socket.io Versione 1.7.5\nIl problema di autenticazione è stato risolto",
"es": "Utiliza socket.io versión 1.7.5\nSe corrigió el problema de autenticación",
"pl": "Użyty socket.io Wersja 1.7.5\nNaprawiono problem z uwierzytelnianiem"
},
"2.1.0": {
"en": "Used socket.io Version 1.7.4",
"de": "Gebrauchte socket.io Version 1.7.4",
"ru": "Используемая версия socket.io 1.7.4",
"pt": "Usado socket.io Versão 1.7.4",
"nl": "Gebruikte socket.io versie 1.7.4",
"fr": "Version socket.io utilisée 1.7.4",
"it": "Usato socket.io Versione 1.7.4",
"es": "Utiliza socket.io versión 1.7.4",
"pl": "Używane socket.io Wersja 1.7.4"
},
"2.0.1": {
"en": "The support of the old browsers was dropped, e.g. iPad 1",
"de": "Die Unterstützung der alten Browser abgebrochen, z.B. iPad 1",
"ru": "Поддержка старых браузеров была прекращена, например. iPad 1",
"pt": "O suporte dos navegadores antigos foi descartado, e. iPad 1",
"nl": "De ondersteuning van de oude browsers is weggevallen, b.v. iPad 1",
"fr": "Le support des anciens navigateurs a été supprimé, par ex. iPad 1",
"it": "Il supporto dei vecchi browser è stato eliminato, ad es. iPad 1",
"es": "El soporte de los navegadores antiguos se eliminó, p. iPad 1",
"pl": "Obsługa starych przeglądarek została usunięta, np. iPad 1"
},
"1.9.0": {
"en": "Ready for Admin3",
"de": "Bereit für Admin3",
"ru": "Готово для администратора3",
"pt": "Pronto para Admin3",
"nl": "Klaar voor Admin3",
"fr": "Prêt pour Admin3",
"it": "Pronto per Admin3",
"es": "Listo para Admin3",
"pl": "Gotowy na administratora3"
},
"1.8.7": {
"en": "Tune cloud work",
"de": "Verbessern die Cloud-Funktionalität",
"ru": "Улучшения для облака"
},
"1.8.5": {
"en": "Escape [] in subscriptions",
"de": "Escape [] in Subscriptions",
"ru": "Escape [] in subscriptions"
},
"1.8.4": {
"en": "Check callback validity",
"de": "Prüfe ob Callback richtig ist",
"ru": "Проверка callbacks"
},
"1.8.3": {
"en": "Allow authentication via URL",
"de": "Erlaube die Authentifizierung via URL",
"ru": "Разрешена авторизация через URL"
},
"1.8.2": {
"en": "Fix cmdExec command",
"de": "Korrigiere cmdExec Komando",
"ru": "Исправлена команда cmdExec"
},
"1.8.1": {
"en": "Fix user access rights for sendToHost",
"de": "Korrigiere die Anwenderrechte für sendToHost",
"ru": "Исправлены права доступа для sendToHost"
},
"1.8.0": {
"en": "Support the access to admin via yunkong2.pro",
"de": "Unterstützung von Zugriff auf admin über yunkong2.pro",
"ru": "Поддержка доступа к admin через yunkong2.pro"
},
"1.7.5": {
"en": "fix error if subscribe is empty",
"de": "Fehler gefixt, falls subscribe ist leer",
"ru": "Исправлена ошибка, если subscribe пуст"
},
"1.7.4": {
"en": "fix error with authentication",
"de": "Korrigiere Authentifizierung",
"ru": "Исправлена аутентификация"
},
"1.7.3": {
"en": "support of socket extensions",
"de": "Unterstützung von Socketerweiterungen",
"ru": "Поддержка плагинов для socket"
},
"1.7.2": {
"en": "Fix unsubscribe of states",
"de": "Korrigiere unsubscribe von Zuständen",
"ru": "Исправлено unsubscribe для состояний"
},
"1.7.1": {
"en": "Fix authentication for app",
"de": "Die Authentifizierung mit APP wurde korrigiert",
"ru": "Аутентификация с app работает снова"
},
"1.7.0": {
"en": "сompatible only with new admin",
"de": "Nur mit neuem Admin kompatibel",
"ru": "Совместимо только с новым админ-драйвером"
},
"1.6.1": {
"en": "fix error by checking user name",
"de": "Fehler mit Namensprüfung behoben",
"ru": "Исправлена ошибка при проверке имени"
},
"1.6.0": {
"en": "support of letsencrypt certificates",
"de": "Unterstützung von letsencrypt Zertifikaten",
"ru": "Поддержка letsencrypt сертификатов"
},
"1.5.4": {
"en": "fix error in socket.js",
"de": "fix error in socket.js",
"ru": "fix error in socket.js"
},
"1.5.3": {
"en": "support of force only web sockets transport",
"de": "support of force only web sockets transport",
"ru": "support of force only web sockets transport"
},
"1.5.2": {
"en": "support of chained certificates",
"de": "support of chained certificates",
"ru": "support of chained certificates"
},
"1.5.1": {
"en": "add sendToHost command",
"de": "add sendToHost command",
"ru": "add sendToHost command"
}
},
"authors": [
"bluefox <dogafox@gmail.com>"
],
"license": "MIT",
"platform": "Javascript/Node.js",
"mode": "daemon",
"loglevel": "info",
"readme": "https://git.spacen.net/yunkong2/yunkong2.socketio/blob/master/README.md",
"icon": "socketio.png",
"keywords": ["web", "socket.io", "communication"],
"enabled": true,
"extIcon": "https://raw.githubusercontent.com/yunkong2/yunkong2.socketio/master/admin/socketio.png",
"type": "communication",
"stopBeforeUpdate": true,
"materialize": true,
"dependencies": [{"js-controller": ">=0.12.0"}]
},
"native": {
"port": 8084,
"auth": false,
"secure": false,
"bind": "0.0.0.0",
"ttl": 3600,
"certPublic": "",
"certPrivate": "",
"certChained": "",
"defaultUser": "admin",
"forceWebSockets": false,
"leEnabled": false,
"leUpdate": false,
"leCheckPort": 80
},
"objects": [
{
"_id": "connected",
"type": "state",
"common": {
"name": "Info about connected socket clients",
"type": "string",
"read": true,
"write": false,
"role": "text"
}
}
]
}

1719
lib/socket.js Normal file

File diff suppressed because it is too large Load Diff

83
lib/utils.js Normal file
View File

@ -0,0 +1,83 @@
'use strict';
const fs = require('fs');
const path = require('path');
let controllerDir;
let appName;
/**
* returns application name
*
* The name of the application can be different and this function finds it out.
*
* @returns {string}
*/
function getAppName() {
const parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 2].split('.')[0];
}
/**
* looks for js-controller home folder
*
* @param {boolean} isInstall
* @returns {string}
*/
function getControllerDir(isInstall) {
// Find the js-controller location
const possibilities = [
'yunkong2.js-controller',
'yunkong2.js-controller',
];
/** @type {string} */
let controllerPath;
for (const pkg of possibilities) {
try {
const possiblePath = require.resolve(pkg);
if (fs.existsSync(possiblePath)) {
controllerPath = possiblePath;
break;
}
} catch (e) { /* not found */ }
}
if (controllerPath == null) {
if (!isInstall) {
console.log('Cannot find js-controller');
process.exit(10);
} else {
process.exit();
}
}
// we found the controller
return path.dirname(controllerPath);
}
/**
* reads controller base settings
*
* @alias getConfig
* @returns {object}
*/
function getConfig() {
let configPath;
if (fs.existsSync(
configPath = path.join(controllerDir, 'conf', appName + '.json')
)) {
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
} else if (fs.existsSync(
configPath = path.join(controllerDir, 'conf', + appName.toLowerCase() + '.json')
)) {
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
} else {
throw new Error('Cannot find ' + controllerDir + '/conf/' + appName + '.json');
}
}
appName = getAppName();
controllerDir = getControllerDir(typeof process !== 'undefined' && process.argv && process.argv.indexOf('--install') !== -1);
const adapter = require(path.join(controllerDir, 'lib/adapter.js'));
exports.controllerDir = controllerDir;
exports.getConfig = getConfig;
exports.Adapter = adapter;
exports.appName = appName;

141
main.js Normal file
View File

@ -0,0 +1,141 @@
/* jshint -W097 */// jshint strict:false
/*jslint node: true */
'use strict';
const utils = require(__dirname + '/lib/utils'); // Get common adapter utils
const IOSocket = require(__dirname + '/lib/socket.js');
const LE = require(utils.controllerDir + '/lib/letsencrypt.js');
let webServer = null;
let store = null;
let secret = 'Zgfr56gFe87jJOM'; // Will be generated by first start
const adapter = new utils.Adapter('socketio');
adapter.on('objectChange', (id, obj) => {
if (webServer && webServer.io) {
webServer.io.publishAll('objectChange', id, obj);
}
});
adapter.on('stateChange', (id, state) => {
if (webServer && webServer.io) {
webServer.io.publishAll('stateChange', id, state);
}
});
adapter.on('unload', callback => {
try {
adapter.log.info('terminating http' + (webServer.settings.secure ? 's' : '') + ' server on port ' + webServer.settings.port);
webServer.io.close();
callback();
} catch (e) {
callback();
}
});
adapter.on('ready', () => {
if (adapter.config.auth) {
// Generate secret for session manager
adapter.getForeignObject('system.config', (err, obj) => {
if (!err && obj) {
if (!obj.native || !obj.native.secret) {
obj.native = obj.native || {};
require('crypto').randomBytes(24, (ex, buf) => {
secret = buf.toString('hex');
adapter.extendForeignObject('system.config', {native: {secret: secret}});
main();
});
} else {
secret = obj.native.secret;
main();
}
} else {
adapter.logger.error('Cannot find object system.config');
}
});
} else {
main();
}
});
adapter.on('log', obj => {
if (webServer && webServer.io) {
webServer.io.sendLog(obj);
}
});
function main() {
if (adapter.config.secure) {
// Load certificates
adapter.getCertificates(function (err, certificates, leConfig) {
adapter.config.certificates = certificates;
adapter.config.leConfig = leConfig;
webServer = initWebServer(adapter.config);
});
} else {
webServer = initWebServer(adapter.config);
}
}
//settings: {
// "port": 8080,
// "auth": false,
// "secure": false,
// "bind": "0.0.0.0", // "::"
//}
function initWebServer(settings) {
const server = {
app: null,
server: null,
io: null,
settings: settings
};
settings.port = parseInt(settings.port, 10) || 0;
if (settings.port) {
if (settings.secure) {
if (!settings.certificates) return null;
}
if (settings.auth) {
const session = require('express-session');
const AdapterStore = require(utils.controllerDir + '/lib/session.js')(session, settings.ttl);
// Authentication checked by server itself
store = new AdapterStore({adapter: adapter});
settings.secret = secret;
settings.store = store;
settings.ttl = settings.ttl || 3600;
settings.forceWebSockets = settings.forceWebSockets || false;
}
adapter.getPort(settings.port, port => {
if (parseInt(port, 10) !== settings.port && !adapter.config.findNextPort) {
adapter.log.error('port ' + settings.port + ' already in use');
process.exit(1);
}
settings.port = port;
server.server = LE.createServer((req, res) => {
res.writeHead(501);
res.end('Not Implemented');
}, settings, adapter.config.certificates, adapter.config.leConfig, adapter.log);
server.server.listen(settings.port, (settings.bind && settings.bind !== '0.0.0.0') ? settings.bind : undefined);
settings.crossDomain = true;
settings.ttl = settings.ttl || 3600;
settings.forceWebSockets = settings.forceWebSockets || false;
server.io = new IOSocket(server.server, settings, adapter);
});
} else {
adapter.log.error('port missing');
process.exit(1);
}
return server;
}

44
package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "yunkong2.socketio",
"version": "2.1.1",
"description": "This adapter allows to communicate different web applications with yunkong2.",
"author": {
"name": "bluefox",
"email": "dogafox@gmail.com"
},
"homepage": "https://git.spacen.net/yunkong2/yunkong2.socketio",
"licenses": [
{
"type": "MIT",
"url": "https://git.spacen.net/yunkong2/yunkong2.socketio/blob/master/LICENSE"
}
],
"keywords": [
"yunkong2",
"web"
],
"repository": {
"type": "git",
"url": "https://git.spacen.net/yunkong2/yunkong2.socketio"
},
"dependencies": {
"socket.io": "1.7.2",
"request": "^2.87.0",
"cookie-parser": "^1.4.3",
"express-session": "^1.15.6"
},
"devDependencies": {
"gulp": "^3.9.1",
"mocha": "^5.2.0",
"chai": "^4.1.2"
},
"bugs": {
"url": "https://git.spacen.net/yunkong2/yunkong2.socketio/issues"
},
"main": "main.js",
"scripts": {
"test": "node node_modules/mocha/bin/mocha --exit"
},
"license": "MIT",
"readmeFilename": "README.md"
}

19
tasks/jscs.js Normal file
View File

@ -0,0 +1,19 @@
var srcDir = __dirname + "/../";
module.exports = {
all: {
src: [
srcDir + "*.js",
srcDir + "lib/*.js",
srcDir + "adapter/**/*.js",
srcDir + "tasks/**/*.js",
srcDir + "www/**/*.js",
'!' + srcDir + "www/lib/**/*.js",
'!' + srcDir + 'node_modules/**/*.js',
'!' + srcDir + 'adapter/admin/www/lib/**/*.js',
'!' + srcDir + 'adapter/*/node_modules/**/*.js',
'!' + srcDir + 'adapter/legacy/www/*/**/*.js'
],
options: require('./jscsRules.js')
}
};

36
tasks/jscsRules.js Normal file
View File

@ -0,0 +1,36 @@
module.exports = {
force: true,
"requireCurlyBraces": ["else", "for", "while", "do", "try", "catch"], /*"if",*/
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"requireSpaceBeforeBlockStatements": true,
"requireParenthesesAroundIIFE": true,
"disallowSpacesInFunctionDeclaration": {"beforeOpeningRoundBrace": true},
"disallowSpacesInNamedFunctionExpression": {"beforeOpeningRoundBrace": true},
"requireSpacesInFunctionExpression": {"beforeOpeningCurlyBrace": true},
"requireSpacesInAnonymousFunctionExpression": {"beforeOpeningRoundBrace": true, "beforeOpeningCurlyBrace": true},
"requireSpacesInNamedFunctionExpression": {"beforeOpeningCurlyBrace": true},
"requireSpacesInFunctionDeclaration": {"beforeOpeningCurlyBrace": true},
"disallowMultipleVarDecl": true,
"requireBlocksOnNewline": true,
"disallowEmptyBlocks": true,
"disallowSpacesInsideObjectBrackets": true,
"disallowSpacesInsideArrayBrackets": true,
"disallowSpaceAfterObjectKeys": true,
"disallowSpacesInsideParentheses": true,
"requireCommaBeforeLineBreak": true,
//"requireAlignedObjectValues": "all",
"requireOperatorBeforeLineBreak": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
// "disallowLeftStickedOperators": ["?", "+", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
// "requireRightStickedOperators": ["!"],
// "requireSpaceAfterBinaryOperators": ["?", "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
//"disallowSpaceAfterBinaryOperators": [","],
"disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"],
"disallowSpaceBeforePostfixUnaryOperators": ["++", "--"],
"requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
"requireSpaceAfterBinaryOperators": ["?", ">", ",", ">=", "<=", "<", "+", "-", "/", "*", "=", "==", "===", "!=", "!=="],
//"validateIndentation": 4,
//"validateQuoteMarks": { "mark": "\"", "escape": true },
"disallowMixedSpacesAndTabs": true,
"disallowKeywordsOnNewLine": ["else", "catch"]
};

19
tasks/jshint.js Normal file
View File

@ -0,0 +1,19 @@
var srcDir = __dirname + "/../";
module.exports = {
options: {
force: true
},
all: [
srcDir + "*.js",
srcDir + "lib/*.js",
srcDir + "adapter/**/*.js",
srcDir + "tasks/**/*.js",
srcDir + "www/**/*.js",
'!' + srcDir + "www/lib/**/*.js",
'!' + srcDir + 'node_modules/**/*.js',
'!' + srcDir + 'adapter/admin/www/lib/**/*.js',
'!' + srcDir + 'adapter/*/node_modules/**/*.js',
'!' + srcDir + 'adapter/legacy/www/*/**/*.js'
]
};

728
test/lib/setup.js Normal file
View File

@ -0,0 +1,728 @@
/* jshint -W097 */// jshint strict:false
/*jslint node: true */
// check if tmp directory exists
var fs = require('fs');
var path = require('path');
var child_process = require('child_process');
var rootDir = path.normalize(__dirname + '/../../');
var pkg = require(rootDir + 'package.json');
var debug = typeof v8debug === 'object';
pkg.main = pkg.main || 'main.js';
var adapterName = path.normalize(rootDir).replace(/\\/g, '/').split('/');
adapterName = adapterName[adapterName.length - 2];
var adapterStarted = false;
function getAppName() {
var parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 3].split('.')[0];
}
var appName = getAppName().toLowerCase();
var objects;
var states;
var pid = null;
function copyFileSync(source, target) {
var targetFile = target;
//if target is a directory a new file with the same name will be created
if (fs.existsSync(target)) {
if ( fs.lstatSync( target ).isDirectory() ) {
targetFile = path.join(target, path.basename(source));
}
}
try {
fs.writeFileSync(targetFile, fs.readFileSync(source));
}
catch (err) {
console.log("file copy error: " +source +" -> " + targetFile + " (error ignored)");
}
}
function copyFolderRecursiveSync(source, target, ignore) {
var files = [];
var base = path.basename(source);
if (base === adapterName) {
base = pkg.name;
}
//check if folder needs to be created or integrated
var targetFolder = path.join(target, base);
if (!fs.existsSync(targetFolder)) {
fs.mkdirSync(targetFolder);
}
//copy
if (fs.lstatSync(source).isDirectory()) {
files = fs.readdirSync(source);
files.forEach(function (file) {
if (ignore && ignore.indexOf(file) !== -1) {
return;
}
var curSource = path.join(source, file);
var curTarget = path.join(targetFolder, file);
if (fs.lstatSync(curSource).isDirectory()) {
// ignore grunt files
if (file.indexOf('grunt') !== -1) return;
if (file === 'chai') return;
if (file === 'mocha') return;
copyFolderRecursiveSync(curSource, targetFolder, ignore);
} else {
copyFileSync(curSource, curTarget);
}
});
}
}
if (!fs.existsSync(rootDir + 'tmp')) {
fs.mkdirSync(rootDir + 'tmp');
}
function storeOriginalFiles() {
console.log('Store original files...');
var dataDir = rootDir + 'tmp/' + appName + '-data/';
var f = fs.readFileSync(dataDir + 'objects.json');
var objects = JSON.parse(f.toString());
if (objects['system.adapter.admin.0'] && objects['system.adapter.admin.0'].common) {
objects['system.adapter.admin.0'].common.enabled = false;
}
if (objects['system.adapter.admin.1'] && objects['system.adapter.admin.1'].common) {
objects['system.adapter.admin.1'].common.enabled = false;
}
fs.writeFileSync(dataDir + 'objects.json.original', JSON.stringify(objects));
try {
f = fs.readFileSync(dataDir + 'states.json');
fs.writeFileSync(dataDir + 'states.json.original', f);
}
catch (err) {
console.log('no states.json found - ignore');
}
}
function restoreOriginalFiles() {
console.log('restoreOriginalFiles...');
var dataDir = rootDir + 'tmp/' + appName + '-data/';
var f = fs.readFileSync(dataDir + 'objects.json.original');
fs.writeFileSync(dataDir + 'objects.json', f);
try {
f = fs.readFileSync(dataDir + 'states.json.original');
fs.writeFileSync(dataDir + 'states.json', f);
}
catch (err) {
console.log('no states.json.original found - ignore');
}
}
function checkIsAdapterInstalled(cb, counter, customName) {
customName = customName || pkg.name.split('.').pop();
counter = counter || 0;
var dataDir = rootDir + 'tmp/' + appName + '-data/';
console.log('checkIsAdapterInstalled...');
try {
var f = fs.readFileSync(dataDir + 'objects.json');
var objects = JSON.parse(f.toString());
if (objects['system.adapter.' + customName + '.0']) {
console.log('checkIsAdapterInstalled: ready!');
setTimeout(function () {
if (cb) cb();
}, 100);
return;
} else {
console.warn('checkIsAdapterInstalled: still not ready');
}
} catch (err) {
}
if (counter > 20) {
console.error('checkIsAdapterInstalled: Cannot install!');
if (cb) cb('Cannot install');
} else {
console.log('checkIsAdapterInstalled: wait...');
setTimeout(function() {
checkIsAdapterInstalled(cb, counter + 1);
}, 1000);
}
}
function checkIsControllerInstalled(cb, counter) {
counter = counter || 0;
var dataDir = rootDir + 'tmp/' + appName + '-data/';
console.log('checkIsControllerInstalled...');
try {
var f = fs.readFileSync(dataDir + 'objects.json');
var objects = JSON.parse(f.toString());
if (objects['system.adapter.admin.0']) {
console.log('checkIsControllerInstalled: installed!');
setTimeout(function () {
if (cb) cb();
}, 100);
return;
}
} catch (err) {
}
if (counter > 20) {
console.log('checkIsControllerInstalled: Cannot install!');
if (cb) cb('Cannot install');
} else {
console.log('checkIsControllerInstalled: wait...');
setTimeout(function() {
checkIsControllerInstalled(cb, counter + 1);
}, 1000);
}
}
function installAdapter(customName, cb) {
if (typeof customName === 'function') {
cb = customName;
customName = null;
}
customName = customName || pkg.name.split('.').pop();
console.log('Install adapter...');
var startFile = 'node_modules/' + appName + '.js-controller/' + appName + '.js';
// make first install
if (debug) {
child_process.execSync('node ' + startFile + ' add ' + customName + ' --enabled false', {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2]
});
checkIsAdapterInstalled(function (error) {
if (error) console.error(error);
console.log('Adapter installed.');
if (cb) cb();
});
} else {
// add controller
var _pid = child_process.fork(startFile, ['add', customName, '--enabled', 'false'], {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2, 'ipc']
});
waitForEnd(_pid, function () {
checkIsAdapterInstalled(function (error) {
if (error) console.error(error);
console.log('Adapter installed.');
if (cb) cb();
});
});
}
}
function waitForEnd(_pid, cb) {
if (!_pid) {
cb(-1, -1);
return;
}
_pid.on('exit', function (code, signal) {
if (_pid) {
_pid = null;
cb(code, signal);
}
});
_pid.on('close', function (code, signal) {
if (_pid) {
_pid = null;
cb(code, signal);
}
});
}
function installJsController(cb) {
console.log('installJsController...');
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller') ||
!fs.existsSync(rootDir + 'tmp/' + appName + '-data')) {
// try to detect appName.js-controller in node_modules/appName.js-controller
// travis CI installs js-controller into node_modules
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller')) {
console.log('installJsController: no js-controller => copy it from "' + rootDir + 'node_modules/' + appName + '.js-controller"');
// copy all
// stop controller
console.log('Stop controller if running...');
var _pid;
if (debug) {
// start controller
_pid = child_process.exec('node ' + appName + '.js stop', {
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2]
});
} else {
_pid = child_process.fork(appName + '.js', ['stop'], {
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
waitForEnd(_pid, function () {
// copy all files into
if (!fs.existsSync(rootDir + 'tmp')) fs.mkdirSync(rootDir + 'tmp');
if (!fs.existsSync(rootDir + 'tmp/node_modules')) fs.mkdirSync(rootDir + 'tmp/node_modules');
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')){
console.log('Copy js-controller...');
copyFolderRecursiveSync(rootDir + 'node_modules/' + appName + '.js-controller', rootDir + 'tmp/node_modules/');
}
console.log('Setup js-controller...');
var __pid;
if (debug) {
// start controller
_pid = child_process.exec('node ' + appName + '.js setup first --console', {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2]
});
} else {
__pid = child_process.fork(appName + '.js', ['setup', 'first', '--console'], {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
waitForEnd(__pid, function () {
checkIsControllerInstalled(function () {
// change ports for object and state DBs
var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
config.objects.port = 19001;
config.states.port = 19000;
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
console.log('Setup finished.');
copyAdapterToController();
installAdapter(function () {
storeOriginalFiles();
if (cb) cb(true);
});
});
});
});
} else {
// check if port 9000 is free, else admin adapter will be added to running instance
var client = new require('net').Socket();
client.connect(9000, '127.0.0.1', function() {
console.error('Cannot initiate fisrt run of test, because one instance of application is running on this PC. Stop it and repeat.');
process.exit(0);
});
setTimeout(function () {
client.destroy();
if (!fs.existsSync(rootDir + 'tmp/node_modules/' + appName + '.js-controller')) {
console.log('installJsController: no js-controller => install from git');
child_process.execSync('npm install https://git.spacen.net/' + appName + '/' + appName + '.js-controller/tarball/master --prefix ./ --production', {
cwd: rootDir + 'tmp/',
stdio: [0, 1, 2]
});
} else {
console.log('Setup js-controller...');
var __pid;
if (debug) {
// start controller
child_process.exec('node ' + appName + '.js setup first', {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2]
});
} else {
child_process.fork(appName + '.js', ['setup', 'first'], {
cwd: rootDir + 'tmp/node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
}
// let npm install admin and run setup
checkIsControllerInstalled(function () {
var _pid;
if (fs.existsSync(rootDir + 'node_modules/' + appName + '.js-controller/' + appName + '.js')) {
_pid = child_process.fork(appName + '.js', ['stop'], {
cwd: rootDir + 'node_modules/' + appName + '.js-controller',
stdio: [0, 1, 2, 'ipc']
});
}
waitForEnd(_pid, function () {
// change ports for object and state DBs
var config = require(rootDir + 'tmp/' + appName + '-data/' + appName + '.json');
config.objects.port = 19001;
config.states.port = 19000;
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/' + appName + '.json', JSON.stringify(config, null, 2));
copyAdapterToController();
installAdapter(function () {
storeOriginalFiles();
if (cb) cb(true);
});
});
});
}, 1000);
}
} else {
setTimeout(function () {
console.log('installJsController: js-controller installed');
if (cb) cb(false);
}, 0);
}
}
function copyAdapterToController() {
console.log('Copy adapter...');
// Copy adapter to tmp/node_modules/appName.adapter
copyFolderRecursiveSync(rootDir, rootDir + 'tmp/node_modules/', ['.idea', 'test', 'tmp', '.git', appName + '.js-controller']);
console.log('Adapter copied.');
}
function clearControllerLog() {
var dirPath = rootDir + 'tmp/log';
var files;
try {
if (fs.existsSync(dirPath)) {
console.log('Clear controller log...');
files = fs.readdirSync(dirPath);
} else {
console.log('Create controller log directory...');
files = [];
fs.mkdirSync(dirPath);
}
} catch(e) {
console.error('Cannot read "' + dirPath + '"');
return;
}
if (files.length > 0) {
try {
for (var i = 0; i < files.length; i++) {
var filePath = dirPath + '/' + files[i];
fs.unlinkSync(filePath);
}
console.log('Controller log cleared');
} catch (err) {
console.error('cannot clear log: ' + err);
}
}
}
function clearDB() {
var dirPath = rootDir + 'tmp/yunkong2-data/sqlite';
var files;
try {
if (fs.existsSync(dirPath)) {
console.log('Clear sqlite DB...');
files = fs.readdirSync(dirPath);
} else {
console.log('Create controller log directory...');
files = [];
fs.mkdirSync(dirPath);
}
} catch(e) {
console.error('Cannot read "' + dirPath + '"');
return;
}
if (files.length > 0) {
try {
for (var i = 0; i < files.length; i++) {
var filePath = dirPath + '/' + files[i];
fs.unlinkSync(filePath);
}
console.log('Clear sqlite DB');
} catch (err) {
console.error('cannot clear DB: ' + err);
}
}
}
function setupController(cb) {
installJsController(function (isInited) {
clearControllerLog();
clearDB();
if (!isInited) {
restoreOriginalFiles();
copyAdapterToController();
}
// read system.config object
var dataDir = rootDir + 'tmp/' + appName + '-data/';
var objs;
try {
objs = fs.readFileSync(dataDir + 'objects.json');
objs = JSON.parse(objs);
}
catch (e) {
console.log('ERROR reading/parsing system configuration. Ignore');
objs = {'system.config': {}};
}
if (!objs || !objs['system.config']) {
objs = {'system.config': {}};
}
if (cb) cb(objs['system.config']);
});
}
function startAdapter(objects, states, callback) {
if (adapterStarted) {
console.log('Adapter already started ...');
if (callback) callback(objects, states);
return;
}
adapterStarted = true;
console.log('startAdapter...');
if (fs.existsSync(rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main)) {
try {
if (debug) {
// start controller
pid = child_process.exec('node node_modules/' + pkg.name + '/' + pkg.main + ' --console silly', {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2]
});
} else {
// start controller
pid = child_process.fork('node_modules/' + pkg.name + '/' + pkg.main, ['--console', 'silly'], {
cwd: rootDir + 'tmp',
stdio: [0, 1, 2, 'ipc']
});
}
} catch (error) {
console.error(JSON.stringify(error));
}
} else {
console.error('Cannot find: ' + rootDir + 'tmp/node_modules/' + pkg.name + '/' + pkg.main);
}
if (callback) callback(objects, states);
}
function startController(isStartAdapter, onObjectChange, onStateChange, callback) {
if (typeof isStartAdapter === 'function') {
callback = onStateChange;
onStateChange = onObjectChange;
onObjectChange = isStartAdapter;
isStartAdapter = true;
}
if (onStateChange === undefined) {
callback = onObjectChange;
onObjectChange = undefined;
}
if (pid) {
console.error('Controller is already started!');
} else {
console.log('startController...');
adapterStarted = false;
var isObjectConnected;
var isStatesConnected;
var Objects = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/objects/objectsInMemServer');
objects = new Objects({
connection: {
"type" : "file",
"host" : "127.0.0.1",
"port" : 19001,
"user" : "",
"pass" : "",
"noFileCache": false,
"connectTimeout": 2000
},
logger: {
silly: function (msg) {
console.log(msg);
},
debug: function (msg) {
console.log(msg);
},
info: function (msg) {
console.log(msg);
},
warn: function (msg) {
console.warn(msg);
},
error: function (msg) {
console.error(msg);
}
},
connected: function () {
isObjectConnected = true;
if (isStatesConnected) {
console.log('startController: started!');
if (isStartAdapter) {
startAdapter(objects, states, callback);
} else {
if (callback) {
callback(objects, states);
callback = null;
}
}
}
},
change: onObjectChange
});
// Just open in memory DB itself
var States = require(rootDir + 'tmp/node_modules/' + appName + '.js-controller/lib/states/statesInMemServer');
states = new States({
connection: {
type: 'file',
host: '127.0.0.1',
port: 19000,
options: {
auth_pass: null,
retry_max_delay: 15000
}
},
logger: {
silly: function (msg) {
console.log(msg);
},
debug: function (msg) {
console.log(msg);
},
info: function (msg) {
console.log(msg);
},
warn: function (msg) {
console.log(msg);
},
error: function (msg) {
console.log(msg);
}
},
connected: function () {
isStatesConnected = true;
if (isObjectConnected) {
console.log('startController: started!!');
if (isStartAdapter) {
startAdapter(objects, states, callback);
} else {
if (callback) {
callback(objects, states);
callback = null;
}
}
}
},
change: onStateChange
});
}
}
function stopAdapter(cb) {
if (!pid) {
console.error('Controller is not running!');
if (cb) {
setTimeout(function () {
cb(false);
}, 0);
}
} else {
adapterStarted = false;
pid.on('exit', function (code, signal) {
if (pid) {
console.log('child process terminated due to receipt of signal ' + signal);
if (cb) cb();
pid = null;
}
});
pid.on('close', function (code, signal) {
if (pid) {
if (cb) cb();
pid = null;
}
});
pid.kill('SIGTERM');
}
}
function _stopController() {
if (objects) {
objects.destroy();
objects = null;
}
if (states) {
states.destroy();
states = null;
}
}
function stopController(cb) {
var timeout;
if (objects) {
console.log('Set system.adapter.' + pkg.name + '.0');
objects.setObject('system.adapter.' + pkg.name + '.0', {
common:{
enabled: false
}
});
}
stopAdapter(function () {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
_stopController();
if (cb) {
cb(true);
cb = null;
}
});
timeout = setTimeout(function () {
timeout = null;
console.log('child process NOT terminated');
_stopController();
if (cb) {
cb(false);
cb = null;
}
pid = null;
}, 5000);
}
// Setup the adapter
function setAdapterConfig(common, native, instance) {
var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
if (common) objects[id].common = common;
if (native) objects[id].native = native;
fs.writeFileSync(rootDir + 'tmp/' + appName + '-data/objects.json', JSON.stringify(objects));
}
// Read config of the adapter
function getAdapterConfig(instance) {
var objects = JSON.parse(fs.readFileSync(rootDir + 'tmp/' + appName + '-data/objects.json').toString());
var id = 'system.adapter.' + adapterName.split('.').pop() + '.' + (instance || 0);
return objects[id];
}
if (typeof module !== undefined && module.parent) {
module.exports.getAdapterConfig = getAdapterConfig;
module.exports.setAdapterConfig = setAdapterConfig;
module.exports.startController = startController;
module.exports.stopController = stopController;
module.exports.setupController = setupController;
module.exports.stopAdapter = stopAdapter;
module.exports.startAdapter = startAdapter;
module.exports.installAdapter = installAdapter;
module.exports.appName = appName;
module.exports.adapterName = adapterName;
module.exports.adapterStarted = adapterStarted;
}

142
test/testAdapter.js Normal file
View File

@ -0,0 +1,142 @@
/* jshint -W097 */// jshint strict:false
/*jslint node: true */
var expect = require('chai').expect;
var setup = require(__dirname + '/lib/setup');
var objects = null;
var states = null;
var onStateChanged = null;
var onObjectChanged = null;
var sendToID = 1;
var adapterShortName = setup.adapterName.substring(setup.adapterName.indexOf('.') + 1);
var runningMode = require(__dirname + '/../io-package.json').common.mode;
function checkConnectionOfAdapter(cb, counter) {
counter = counter || 0;
console.log('Try check #' + counter);
if (counter > 30) {
if (cb) cb('Cannot check connection');
return;
}
states.getState('system.adapter.' + adapterShortName + '.0.alive', function (err, state) {
if (err) console.error(err);
if (state && state.val) {
if (cb) cb();
} else {
setTimeout(function () {
checkConnectionOfAdapter(cb, counter + 1);
}, 1000);
}
});
}
function checkValueOfState(id, value, cb, counter) {
counter = counter || 0;
if (counter > 20) {
if (cb) cb('Cannot check value Of State ' + id);
return;
}
states.getState(id, function (err, state) {
if (err) console.error(err);
if (value === null && !state) {
if (cb) cb();
} else
if (state && (value === undefined || state.val === value)) {
if (cb) cb();
} else {
setTimeout(function () {
checkValueOfState(id, value, cb, counter + 1);
}, 500);
}
});
}
function sendTo(target, command, message, callback) {
onStateChanged = function (id, state) {
if (id === 'messagebox.system.adapter.test.0') {
callback(state.message);
}
};
states.pushMessage('system.adapter.' + target, {
command: command,
message: message,
from: 'system.adapter.test.0',
callback: {
message: message,
id: sendToID++,
ack: false,
time: (new Date()).getTime()
}
});
}
describe('Test ' + adapterShortName + ' adapter', function() {
before('Test ' + adapterShortName + ' adapter: Start js-controller', function (_done) {
this.timeout(600000); // because of first install from npm
setup.setupController(function () {
var config = setup.getAdapterConfig();
// enable adapter
config.common.enabled = true;
config.common.loglevel = 'debug';
//config.native.dbtype = 'sqlite';
setup.setAdapterConfig(config.common, config.native);
setup.startController(true, function(id, obj) {}, function (id, state) {
if (onStateChanged) onStateChanged(id, state);
},
function (_objects, _states) {
objects = _objects;
states = _states;
_done();
});
});
});
it('Test ' + adapterShortName + ' instance object: it must exists', function (done) {
objects.getObject('system.adapter.' + adapterShortName + '.0', function (err, obj) {
expect(err).to.be.null;
expect(obj).to.be.an('object');
expect(obj).not.to.be.null;
done();
});
});
it('Test ' + adapterShortName + ' adapter: Check if adapter started', function (done) {
this.timeout(60000);
checkConnectionOfAdapter(function (res) {
if (res) console.log(res);
if (runningMode === 'daemon') {
expect(res).not.to.be.equal('Cannot check connection');
} else {
//??
}
done();
});
});
/**/
/*
PUT YOUR OWN TESTS HERE USING
it('Testname', function ( done) {
...
});
You can also use "sendTo" method to send messages to the started adapter
*/
after('Test ' + adapterShortName + ' adapter: Stop js-controller', function (done) {
this.timeout(10000);
setup.stopController(function (normalTerminated) {
console.log('Adapter normal terminated: ' + normalTerminated);
done();
});
});
});

91
test/testPackageFiles.js Normal file
View File

@ -0,0 +1,91 @@
/* jshint -W097 */
/* jshint strict:false */
/* jslint node: true */
/* jshint expr: true */
var expect = require('chai').expect;
var fs = require('fs');
describe('Test package.json and io-package.json', function() {
it('Test package files', function (done) {
console.log();
var fileContentIOPackage = fs.readFileSync(__dirname + '/../io-package.json', 'utf8');
var ioPackage = JSON.parse(fileContentIOPackage);
var fileContentNPMPackage = fs.readFileSync(__dirname + '/../package.json', 'utf8');
var npmPackage = JSON.parse(fileContentNPMPackage);
expect(ioPackage).to.be.an('object');
expect(npmPackage).to.be.an('object');
expect(ioPackage.common.version, 'ERROR: Version number in io-package.json needs to exist').to.exist;
expect(npmPackage.version, 'ERROR: Version number in package.json needs to exist').to.exist;
expect(ioPackage.common.version, 'ERROR: Version numbers in package.json and io-package.json needs to match').to.be.equal(npmPackage.version);
if (!ioPackage.common.news || !ioPackage.common.news[ioPackage.common.version]) {
console.log('WARNING: No news entry for current version exists in io-package.json, no rollback in Admin possible!');
console.log();
}
expect(npmPackage.author, 'ERROR: Author in package.json needs to exist').to.exist;
expect(ioPackage.common.authors, 'ERROR: Authors in io-package.json needs to exist').to.exist;
if (ioPackage.common.name.indexOf('template') !== 0) {
if (Array.isArray(ioPackage.common.authors)) {
expect(ioPackage.common.authors.length, 'ERROR: Author in io-package.json needs to be set').to.not.be.equal(0);
if (ioPackage.common.authors.length === 1) {
expect(ioPackage.common.authors[0], 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
}
}
else {
expect(ioPackage.common.authors, 'ERROR: Author in io-package.json needs to be a real name').to.not.be.equal('my Name <my@email.com>');
}
}
else {
console.log('WARNING: Testing for set authors field in io-package skipped because template adapter');
console.log();
}
expect(fs.existsSync(__dirname + '/../README.md'), 'ERROR: README.md needs to exist! Please create one with description, detail information and changelog. English is mandatory.').to.be.true;
if (!ioPackage.common.titleLang || typeof ioPackage.common.titleLang !== 'object') {
console.log('WARNING: titleLang is not existing in io-package.json. Please add');
console.log();
}
if (
ioPackage.common.title.indexOf('yunkong2') !== -1 ||
ioPackage.common.title.indexOf('yunkong2') !== -1 ||
ioPackage.common.title.indexOf('adapter') !== -1 ||
ioPackage.common.title.indexOf('Adapter') !== -1
) {
console.log('WARNING: title contains Adapter or yunkong2. It is clear anyway, that it is adapter for yunkong2.');
console.log();
}
if (ioPackage.common.name.indexOf('vis-') !== 0) {
if (!ioPackage.common.materialize || !fs.existsSync(__dirname + '/../admin/index_m.html') || !fs.existsSync(__dirname + '/../gulpfile.js')) {
console.log('WARNING: Admin3 support is missing! Please add it');
console.log();
}
if (ioPackage.common.materialize) {
expect(fs.existsSync(__dirname + '/../admin/index_m.html'), 'Admin3 support is enabled in io-package.json, but index_m.html is missing!').to.be.true;
}
}
var licenseFileExists = fs.existsSync(__dirname + '/../LICENSE');
var fileContentReadme = fs.readFileSync(__dirname + '/../README.md', 'utf8');
if (fileContentReadme.indexOf('## Changelog') === -1) {
console.log('Warning: The README.md should have a section ## Changelog');
console.log();
}
expect((licenseFileExists || fileContentReadme.indexOf('## License') !== -1), 'A LICENSE must exist as LICENSE file or as part of the README.md').to.be.true;
if (!licenseFileExists) {
console.log('Warning: The License should also exist as LICENSE file');
console.log();
}
if (fileContentReadme.indexOf('## License') === -1) {
console.log('Warning: The README.md should also have a section ## License to be shown in Admin3');
console.log();
}
done();
});
});