diff --git a/README.md b/README.md index 86ede55..b6f29fc 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ var sqlite = require('sqlite3-server'); var opts = { key: '1892d335081d8d346e556c9c3c8ff2c3' , bits: 128 -, storage: path.join('/tmp/authn.sqlcipher') +, filename: path.join('/tmp/authn.sqlcipher') , verbose: false , port: 3232 // default random , forceServer: true // default false diff --git a/client.js b/client.js index 429d26d..015fd34 100644 --- a/client.js +++ b/client.js @@ -1,3 +1,141 @@ 'use strict'; +/*global Promise*/ +// TODO iterate over the prototype +// translate request / response +var sqlite3real = require('sqlite3'); + +/* +function createConnection(opts) { + var server = ; + + return server.create(opts).then(function () { + // created and listening + }); +} +*/ + +function startServer(opts) { + return require('./server').create(opts).then(function (server) { + // this process doesn't need to connect to itself + // through a socket + return server.masterClient; + }); +} + +function getConnection(opts) { + if (!opts.sock) { + opts.sock = opts.filename + '.sock'; + } + + return new Promise(function (resolve) { + setTimeout(function () { + var WebSocket = require('ws'); + var ws = new WebSocket('ws+unix:' + opts.sock); + + if (opts.server) { + return startServer(opts); + } + + ws.on('error', function (err) { + console.error('[ERROR] ws connection failed, retrying'); + console.error(err); + + function retry() { + setTimeout(function () { + getConnection(opts).then(resolve); + }, 100 + (Math.random() * 250)); + } + + if ('ENOENT' === err.code || 'ECONNREFUSED' === err.code) { + return startServer(opts).then(function (client) { + // ws.masterClient = client; + resolve({ masterClient: client }); + }, function () { + retry(); + }); + } + + retry(); + }); + + ws.on('open', function () { + resolve(ws); + }); + }); + }, 100 + (Math.random() * 250)); +} + +function create(opts) { + // TODO maybe use HTTP POST instead? + return getConnection(opts).then(function (ws) { + if (ws.masterClient) { + return ws.masterClient; + } + + var db = {}; + var proto = sqlite3real.Database.prototype; + + function rpc(fname, args) { + var id; + var cb; + + if ('function' === typeof args[args.length - 1]) { + id = Math.random(); + cb = args.pop(); + } + + ws.send({ + type: 'rpc' + , func: fname + , args: args + , filename: opts.filename + , id: id + }); + + if (!cb) { + return; + } + + function onMessage(data) { + if (!data || 'object' !== typeof data) { + return; + } + + if (data.id !== id) { + return; + } + + cb.apply(data.this, data.args); + } + + ws.on('message', onMessage); + } + + db.sanitize = require('./wrapper').sanitize; + + Object.keys(sqlite3real.Database.prototype).forEach(function (key) { + + if ('function' === typeof proto[key]) { + db[key] = function () { + rpc(key, Array.prototype.slice.call(arguments)); + }; + } + + }); + + + // serialize + // parallel + db.serialize = db.parallel = function () { + throw new Error('NOT IMPLEMENTED in SQLITE3-remote'); + }; + + return db; + }); +} + + +module.exports.sanitize = require('./wrapper').sanitize; +module.exports.create = create; diff --git a/index.js b/index.js index eeefd71..aad176a 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ var numcpus = require('os').cpus().length; var sqlite3; -if (false && numcpus >= 2) { +if (numcpus >= 2) { sqlite3 = require('./client'); } else { sqlite3 = require('./wrapper'); diff --git a/server.js b/server.js index 8829198..fbe2000 100644 --- a/server.js +++ b/server.js @@ -1,54 +1,84 @@ 'use strict'; +/*global Promise*/ -function create(options) { - var url = require('url'); - var express = require('express'); - var app = express(); - var wss = options.wss; +var wsses = {}; - wss.on('connection', function (ws) { - var location = url.parse(ws.upgradeReq.url, true); - // you might use location.query.access_token to authenticate or share sessions - // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312 +function createApp(server, options) { + console.log('Create App'); - ws.__session_id = location.query.session_id || Math.random(); + if (wsses[options.filename]) { + return Promise.resolve(wsses[options.filename]); + } - ws.on('message', function (buffer) { - var cmd; + return require('./wrapper').create(options).then(function (db) { + var url = require('url'); + var express = require('express'); + var app = express(); + var wss = server.wss; - try { - cmd = JSON.parse(buffer.toString('utf8')); - } catch(e) { - ws.send(JSON.stringify({ type: 'error', value: { message: e.message, code: "E_PARSE_JSON" } })); - } + wss.on('connection', function (ws) { + var location = url.parse(ws.upgradeReq.url, true); + // you might use location.query.access_token to authenticate or share sessions + // or ws.upgradeReq.headers.cookie (see http://stackoverflow.com/a/16395220/151312 - switch(cmd.type) { - case 'init': - break; + ws.__session_id = location.query.session_id || Math.random(); - case 'rpc': - break; + ws.on('message', function (buffer) { + var cmd; - default: - break; - } + try { + cmd = JSON.parse(buffer.toString('utf8')); + } catch(e) { + ws.send(JSON.stringify({ type: 'error', value: { message: e.message, code: "E_PARSE_JSON" } })); + } + switch(cmd.type) { + case 'init': + break; + + case 'rpc': + break; + + default: + break; + } + + }); + + ws.send(JSON.stringify({ type: 'session', value: ws.__session_id })); }); - ws.send(JSON.stringify({ type: 'session', value: ws.__session_id })); + app.masterClient = db; + wsses[options.filename] = app; + + return app; }); +} - /* - var tablename = 'authn'; - if (tablename) { - setup.push(db.runAsync("CREATE TABLE IF NOT EXISTS '" + sanitize(tablename) - + "' (id TEXT, secret TEXT, json TEXT, PRIMARY KEY(id))")); - } - */ +function create(options) { + console.log('Create Server'); - /*global Promise*/ return new Promise(function (resolve) { - resolve(app); + var server = require('http').createServer(); + var WebSocketServer = require('ws').Server; + var wss = new WebSocketServer({ server: server }); + //var port = process.env.PORT || process.argv[0] || 4080; + + console.log('options.sock'); + console.log(options.sock); + var fs = require('fs'); + fs.unlink(options.sock, function () { + // ignore error when socket doesn't exist + + server.listen(options.sock, function () { + console.log('Listening'); + }); + }); + + createApp({ server: server, wss: wss }, options).then(function (app) { + server.on('request', app); + resolve({ masterClient: app.masterClient }); + }); }); } diff --git a/wrapper.js b/wrapper.js index 33bb334..76bd9ff 100644 --- a/wrapper.js +++ b/wrapper.js @@ -19,11 +19,12 @@ function create(opts) { sqlite3.verbose(); } - if (!dbs[opts.storage] || dbs[opts.storage].__key !== opts.key) { - dbs[opts.storage] = new sqlite3.Database(opts.storage); + if (!dbs[opts.filename] || dbs[opts.filename].__key !== opts.key) { + dbs[opts.filename] = new sqlite3.Database(opts.filename); } - db = dbs[opts.storage]; + db = dbs[opts.filename]; + db.sanitize = sanitize; db.__key = opts.key; return new Promise(function (resolve, reject) {