switch npm published hpx client; clean up code;

This commit is contained in:
Twiddly 2019-09-17 18:15:12 +02:00
parent c4fe6eef0b
commit 22fcb10196
14 changed files with 14287 additions and 6141 deletions

1
.gitignore vendored
View File

@ -10,4 +10,5 @@
/static/Thumbs.db
/data
/dist
/dist-cache
/desktop.json

15
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "NWjs",
"port": 9345,
"restart": true
}
]
}

27
app/bg.js Normal file
View File

@ -0,0 +1,27 @@
function main() {
var menu = new nw.Menu();
// Add some items with label
menu.append(new nw.MenuItem({
label: 'Item A',
click: function(){
alert('You have clicked at "Item A"');
}
}));
menu.append(new nw.MenuItem({ label: 'Item B' }));
menu.append(new nw.MenuItem({ type: 'separator' }));
menu.append(new nw.MenuItem({ label: 'Item C' }));
// Hooks the "contextmenu" event
win.window.document.body.addEventListener('contextmenu', function(ev) {
// Prevent showing default context menu
ev.preventDefault();
// Popup the native context menu at place you click
menu.popup(ev.x, ev.y);
return false;
}, false);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,414 +0,0 @@
const net = require('net');
const zlib = require('zlib');
const { Transform } = require('stream');
const log = require('./log');
//const Promise = require('./bluebird');
const fs = require('fs');
process.on('unhandledRejection', (reason, promise) => {
console.log('Unhandled Rejection:')
console.log(reason)
console.log('Unhandled Rejection at:', reason.stack || reason)
// Recommended: send the information to sentry.io
// or whatever crash reporting service you use
})
//Promise.longStackTraces();
const encoding = "utf8"
const postfix = Buffer.from("<EOF>", encoding)
const exception_codes = {
'AuthRequiredError': 407,
'AuthWrongCredentialsError': 411,
'AuthMissingCredentials': 412,
'ServerError': 400,
'AuthError': 406,
'ClientError': 500,
'ConnectionError': 501,
'ServerDisconnectError': 502,
}
class EError extends Error {
constructor(message) {
super(message);
this.name = this.constructor.name;
if (typeof Error.captureStackTrace === 'function') {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = (new Error(message)).stack;
}
this.code = exception_codes[this.name];
}
}
class ServerError extends EError { }
exports.ServerError = ServerError
class AuthError extends ServerError {}
exports.AuthError = AuthError
class AuthWrongCredentialsError extends AuthError {}
exports.AuthWrongCredentialsError = AuthWrongCredentialsError
class AuthRequiredError extends AuthError {}
exports.AuthRequiredError = AuthRequiredError
class AuthMissingCredentials extends AuthError {}
exports.AuthMissingCredentials = AuthMissingCredentials
class ClientError extends ServerError {}
exports.ClientError = ClientError
class ConnectionError extends ClientError {}
exports.ConnectionError = ConnectionError
class ServerDisconnectError extends ConnectionError {}
exports.ServerDisconnectError = ServerDisconnectError
function finalize(msg_dict, session_id, name, error, opts) {
session_id = session_id || ""
if (opts === undefined)
opts = {}
if (name === undefined)
name = null
if (error === undefined)
error = null
let to_json = opts.to_json
if (opts.to_json === undefined)
to_json = false
let to_bytes = opts.to_bytes
if (opts.to_bytes === undefined)
to_bytes = false
if (opts.to_bytes === true)
to_json = true
let msg = {
'session': session_id,
'name': name ? name : "hpx-desktop",
'data': msg_dict,
}
if (error)
msg['error'] = error
rmsg = msg
if (to_json)
rmsg = JSON.stringify(rmsg)
if (to_bytes)
rmsg = Buffer.from(rmsg, encoding)
return rmsg
}
class HPXTransform extends Transform {
constructor(client_name, options) {
super(options);
this._buffer = Buffer.from("", encoding)
this._client_name = client_name
}
_end_of_msg(buffer) {
let chunk = buffer
let idx = buffer.indexOf(postfix)
let eof = false
let remaining_buffer = Buffer.from("", encoding)
if (idx !== -1) {
chunk = buffer.slice(0, idx)
remaining_buffer = buffer.slice(idx+postfix.length)
eof = true
}
return [[chunk, remaining_buffer], eof]
}
_transform(chunk, enc, callback) {
let err = null
let data = null
if (!chunk.length)
err = new ServerDisconnectError("Server disconnected for client '" + this._client_name + "'")
this._buffer = Buffer.concat([this._buffer, chunk])
let [splitted_data, eof] = this._end_of_msg(this._buffer)
if (eof) {
data = splitted_data[0]
this._buffer = splitted_data[1]
}
if (data) {
zlib.unzip(data, (zip_err, zip_buffer) => {
if (!zip_err) {
log.d("Recevied " + zip_buffer.length.toString() + " bytes from server")
callback(err, zip_buffer)
}
else
log.e(zip_err.message)
})
}
else
callback(err)
}
}
let options;
function get_server() {
if (!options && fs.existsSync(global.CONFIG_PATH)) {
let data = fs.readFileSync(global.CONFIG_PATH, 'utf8')
if (data) {
options = JSON.parse(data)
if (options.c_server_host !== undefined) {
global.SERVER.HOST = options.c_server_host
}
if (options.c_server_port !== undefined) {
global.SERVER.PORT = options.c_server_port
}
}
}
return [global.SERVER.HOST, global.SERVER.PORT]
}
class Client {
constructor(name, server, session_id, client_id) {
this.id = client_id || "hpx-desktop"
this.name = name || "hpx-desktop"
this._server = server || get_server()
this._alive = false;
this._ready = false;
this._buffer = Buffer.from("", encoding)
this.session = session_id || ""
this.version = null;
this.guest_allowed = false;
this._accepted = false;
this._last_user = ""
this._last_pass = ""
this._stream = new HPXTransform(this._name, {
'defaultEncoding ': encoding
})
this._stream.on("data", this._recv.bind(this))
this.timeout = 10
this._sock = null;
this._current_callback = null
this._first_message = true
this._promise_resolves = []
this._on_connect_promise_resolves = []
this._connecting = false
this._resolved_connected_promises = false
this._create_socket()
}
_create_socket() {
this._sock = new net.Socket();
this._sock.setTimeout(this.timeout, this._on_timeout.bind(this))
this._sock.on("data", data => this._stream.write(data))
this._sock.on("connect", this._on_connect.bind(this))
this._sock.on("close", this._on_disconnect.bind(this))
this._sock.on("end", this._on_disconnect.bind(this))
this._sock.on("error", this._on_error.bind(this))
}
alive() {
return this._alive;
}
ready() {
return this._ready;
}
_server_info(data) {
if (data) {
let serv_data = data.data
if (serv_data && serv_data.hasOwnProperty("version")) {
this.guest_allowed = serv_data.guest_allowed || false
this.version = serv_data.version
this._ready = true
}
}
}
set_server(server) {
this._server = server
}
async handshake(data, user, password, ignore_err) {
if (this.alive()) {
if (password === undefined)
password = null
if (user) {
this._last_user = user
this._last_pass = password
}
if (!ignore_err && data) {
let serv_error = data.error
if (serv_error) {
if (serv_error.code === exception_codes['AuthWrongCredentialsError'])
throw new AuthWrongCredentialsError(serv_error.msg);
else if (serv_error.code === exception_codes['AuthRequiredError'])
throw new AuthRequiredError(serv_error.msg);
else if (serv_error.code === exception_codes['AuthMissingCredentials'])
throw new AuthMissingCredentials(serv_error.msg);
else
throw new AuthError(serv_error.code.toString() + ': ' + serv_error.msg);
}
}
if (!data) {
let d = {}
if (user) {
d.user = user
d.password = password
}
let serv_data = await this.send_raw(finalize(d, null, this.name, undefined))
await this.handshake(serv_data, null, null, ignore_err)
}
else if (data) {
let serv_data = data.data;
if (serv_data === "Authenticated") {
this.session = data.session
this._accepted = true
}
}
}
}
async request_auth(ignore_err) {
let data = await this.send({
'session': "", 'name': this.name,
'data': 'requestauth'
})
this._server_info(data)
await this.handshake(null, this._last_user, this._last_pass, ignore_err)
}
is_connected() {
return self.alive()
}
async connect() {
let p = new Promise((resolve, reject) => {
if (!this._alive && !this._connecting) {
this._on_connect_promise_resolves.unshift([resolve, reject])
log.i("Client connecting to server at: " + JSON.stringify(this._server))
this._connecting = true
this._sock.connect(this._server[1], this._server[0], () => {
this._connecting = false
this._alive = true
if (this.session)
this._accepted = true
})
}
else
resolve(false)
})
await p
return this.alive()
}
_on_timeout() {
}
_on_connect() {
log.i("Client successfully connected to server at: " + JSON.stringify(this._server))
}
_on_error(error) {
this._connecting = false
log.e(error.message)
if (this._on_connect_promise_resolves.length) {
let [_resolve, _reject] = this._on_connect_promise_resolves.shift()
_reject(error)
}
this._disconnect()
}
_on_disconnect() {
this._disconnect()
}
_disconnect() {
this._alive = false
this._accepted = false
this.session = ""
this._ready = false
this._first_message = true
}
async send(msg) {
return this._send(finalize(msg, this.session, this.name, undefined, {to_bytes:true, to_json:true}))
}
async send_raw(msg) {
return this._send(Buffer.from(JSON.stringify(msg), encoding))
}
async _send(msg_bytes) {
let p = new Promise((resolve, reject) => {
if (!this._alive) {
throw new ClientError("Client '" + this.name + "' is not connected to server")
}
log.d("Sending " + msg_bytes.length.toString() + " bytes to server " + JSON.stringify(this._server))
zlib.gzip(msg_bytes, (err, buffer) => {
if (!err) {
console.log("Sending data server")
this._sock.write(buffer)
this._sock.write(postfix)
} else {
reject(err)
}
this._promise_resolves.unshift([resolve, reject])
});
})
return p
}
_recv(data) {
let parsed_data = JSON.parse(data.toString())
if (this._first_message) {
this._first_message = false
this._server_info(parsed_data)
}
if (this._on_connect_promise_resolves.length) {
while (this._on_connect_promise_resolves.length) {
let [_resolve, _reject] = this._on_connect_promise_resolves.shift()
_resolve(true)
}
if (!this._resolved_connected_promises) {
this._resolved_connected_promises = true
return
}
}
if (this._promise_resolves.length) {
let [resolve, reject] = this._promise_resolves.shift()
resolve(parsed_data)
return
}
}
close() {
log.i("Closing connection to server")
this._disconnect()
this._sock.end()
}
}
exports.Client = Client

View File

View File

@ -1,4 +1,4 @@
const client = require('../app/client');
const hpxclient = require('happypandax-client');
const fs = require('fs');
const path = require('path');
const child_process = require('child_process');
@ -85,8 +85,8 @@ function main() {
async function check_connection(params) {
if (!client_obj)
client_obj = new client.Client()
client_obj.set_server([params.server_host, params.server_port])
client_obj = new hpxclient.Client()
client_obj.set_server(params.server_host, params.server_port)
try {
return await client_obj.connect()
} catch (e) {

View File

@ -8,7 +8,7 @@ nw.Window.open(
"icon": "static/favicon/favicon.ico",
"min_width": 500,
"min_height": 500,
'inject_js_start': 'app/start.js'
'inject_js_start': 'app/start.js',
},
function (win) {
@ -17,14 +17,14 @@ nw.Window.open(
this.close(true)
});
global.contextmenu = new nw.Menu();
global.contextmenu.createMacBuiltin("HappyPanda X Desktop");
global.contextmenu.append(new nw.MenuItem({ label: 'Quit' }));
// global.contextmenu = new nw.Menu();
// global.contextmenu.createMacBuiltin("HappyPanda X Desktop");
// global.contextmenu.append(new nw.MenuItem({ label: 'Quit' }));
//win.window.document.body.addEventListener('contextmenu', function (ev) {
// win.window.document.body.addEventListener('contextmenu', function (ev) {
// ev.preventDefault();
// global.contextmenu.popup(ev.x, ev.y);
// return false;
//});
// });
});

View File

@ -1,11 +1,30 @@
const EventEmitter = require('events');
const client = require('./client');
const log = require('./log');
//const Promise = require('./bluebird');
const crypto = require('crypto');
const fs = require('fs');
const hpxclient = require('happypandax-client');
const log = require('./log');
const AsyncLock = require('./async_lock');
//Promise.longStackTraces();
let options;
function get_server() {
if (!options && fs.existsSync(global.CONFIG_PATH)) {
let data = fs.readFileSync(global.CONFIG_PATH, 'utf8')
if (data) {
options = JSON.parse(data)
if (options.c_server_host !== undefined) {
global.SERVER.HOST = options.c_server_host
}
if (options.c_server_port !== undefined) {
global.SERVER.PORT = options.c_server_port
}
}
}
return [global.SERVER.HOST, global.SERVER.PORT]
}
(function () {
@ -18,11 +37,15 @@ const AsyncLock = require('./async_lock');
let all_emitters = {}
function _create_clients(id, session_id) {
let [host, port] = get_server()
session_id = session_id || ""
all_clients[id] = {
"client": new client.Client("webclient", undefined, session_id, id),
"notification": new client.Client("notification", undefined, session_id, id),
"command": new client.Client("command", undefined, session_id, id)
"client": new hpxclient.Client({name:"webclient", session_id, host, port}),
"notification": new hpxclient.Client({name:"webclient", session_id, host, port}),
"thumbnail": new hpxclient.Client({name:"thumbnail", session_id, host, port}),
"command": new hpxclient.Client({name:"webclient", session_id, host, port})
}
}
@ -48,6 +71,11 @@ const AsyncLock = require('./async_lock');
return p
}
async function _logout_clients(clients) {
for (var name in clients)
await clients[name].logout()
}
async function _handshake_clients(clients, username, password, request) {
if (!clients[main_client].alive()) {
await clients[main_client].connect()
@ -205,12 +233,14 @@ const AsyncLock = require('./async_lock');
'status': 4,
'handshake': 5,
'rehandshake': 6,
'logout': 7,
}
let d = {
'status': null,
'accepted': null,
'version': {},
'session': '',
'guest_allowed': null,
'id': msg.id || null
}
@ -230,7 +260,7 @@ const AsyncLock = require('./async_lock');
try {
await _connect_clients(clients)
} catch (e) {
if (e instanceof client.ClientError)
if (e instanceof hpxclient.ClientError)
send_error(client_id, e)
else
throw e
@ -244,6 +274,9 @@ const AsyncLock = require('./async_lock');
else if (cmd == commands['status']) {
null
}
else if (cmd == commands['logout']) {
await _logout_clients(clients)
}
try {
if (cmd == commands['handshake']) {
@ -254,7 +287,7 @@ const AsyncLock = require('./async_lock');
}
} catch (e) {
if (e instanceof client.AuthError)
if (e instanceof hpxclient.AuthError)
send_error(client_id, e)
else
throw e
@ -266,9 +299,10 @@ const AsyncLock = require('./async_lock');
d['accepted'] = clients['client']._accepted
d['guest_allowed'] = clients['client'].guest_allowed
d['version'] = clients['client'].version
d['session'] = clients['client'].session
} catch (e) {
if (e instanceof client.ServerError)
if (e instanceof hpxclient.ServerError)
send_error(client_id, e)
else
throw e

View File

@ -29,3 +29,26 @@ function IS_SAME_MACHINE(host) {
});
return same;
}
class _DESKTOP_API {
constructor() {
this.context_menu = this.create_context_menu()
}
show_context_menu(ev) {
if (this.context_menu) {
this.context_menu.popup(ev.x, ev.y);
return false
}
}
create_context_menu() {
let menu = new nw.Menu();
menu.append(new nw.MenuItem({ label: 'Quit' }))
return menu
}
}
const DESKTOP_API = new _DESKTOP_API()

View File

@ -1,25 +0,0 @@
const theGlobal = typeof window === 'object' ? window : global;
const realPromiseConstructor = theGlobal.Promise;
const wrappedPromiseConstructor = function (resolve, reject, progress) {
const originalPromiseInstance = new realPromiseConstructor(resolve, reject, progress);
// Who called us? Let's store it.
const stackWhenCalled = new Error().stack;
const wrappedPromiseInstance = originalPromiseInstance.catch((err) => {
try {
err.stack = err.stack || '';
err.stack += '\nDuring promise started:\n' + stackWhenCalled.split('\n').slice(3).join('\n');
} catch (err2) {
console.error("promiseDebugging.reportLongStackTraces had difficulty adding to the stack:", err2);
}
return realPromiseConstructor.reject(err);
});
return wrappedPromiseInstance;
};
Object.setPrototypeOf(wrappedPromiseConstructor, realPromiseConstructor);
theGlobal.Promise = wrappedPromiseConstructor;

7
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "happypandax-desktop",
"version": "0.1.0",
"version": "0.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -420,6 +420,11 @@
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=",
"dev": true
},
"happypandax-client": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/happypandax-client/-/happypandax-client-1.0.1.tgz",
"integrity": "sha512-XfKL4dKHShtcym0bcnbSZcn2p/75zANY2X3FOsp7xNluhjh43LiAU+2cdQ/QEa/hoaPEP+Trvy2uwrmVB38d9A=="
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",

View File

@ -1,7 +1,7 @@
{
"name": "happypandax-desktop",
"description": "A client for HappyPanda X on the desktop",
"version": "0.1.1",
"description": "HappyPanda X Desktop",
"version": "0.2.0",
"product_string": "HappyPanda X Desktop",
"main": "app/main.js",
"user-agent": "HappyPanda X Desktop/%ver/%osinfo",
@ -13,17 +13,34 @@
"version": "\"node -pe \"require('./package.json').version\"\""
},
"domain": "happypandax.desktop",
"chromium-args": "--mixed-context --enable-node-worker --user-data-dir=data --windows10-custom-titlebar --dark_muted --desktop-window-1080p --enable-smooth-scrolling --fast --hide-scrollbars --homedir=./",
"dependencies": {},
"chromium-args": "--remote-debugging-port=9324 --mixed-context --enable-node-worker --user-data-dir=data --windows10-custom-titlebar --dark_muted --desktop-window-1080p --enable-smooth-scrolling --fast --hide-scrollbars --homedir=./",
"dependencies": {
"happypandax-client": "^1.0.1"
},
"devDependencies": {
"nwjs-builder-phoenix": "^1.15.0"
},
"window": {
"icon": "static/favicon/mstile-144x144.png"
},
"build": {
"nwVersion": "stable",
"nwFlavor": "normal",
"files": [ "static/**/*", "app/**/*", "templates/**/*" ],
"excludes": [ "**/Thumbs.db" ],
"targets": [ "zip", "nsis" ],
"overriddenProperties": {
"chromium-args": "--mixed-context --enable-node-worker --user-data-dir=data --windows10-custom-titlebar --dark_muted --desktop-window-1080p --enable-smooth-scrolling --fast --hide-scrollbars --homedir=./"
},
"files": [
"static/**/*",
"app/**/*",
"templates/**/*"
],
"excludes": [
"**/Thumbs.db"
],
"targets": [
"zip",
"nsis"
],
"outputPattern": "${NAME}-${VERSION}-${PLATFORM}-${ARCH}",
"appId": "com.happypandax.desktop",
"win": {

File diff suppressed because one or more lines are too long