diff --git a/packages/nylas-api/app.js b/packages/nylas-api/app.js index 365e53904..29c4ee2f0 100644 --- a/packages/nylas-api/app.js +++ b/packages/nylas-api/app.js @@ -7,6 +7,8 @@ const Package = require('./package'); const fs = require('fs'); const path = require('path'); +global.Promise = require('bluebird'); + const server = new Hapi.Server(); server.connection({ port: process.env.PORT || 5100 }); diff --git a/packages/nylas-core/imap-connection.js b/packages/nylas-core/imap-connection.js index 11782518a..8a047f830 100644 --- a/packages/nylas-core/imap-connection.js +++ b/packages/nylas-core/imap-connection.js @@ -1,6 +1,5 @@ const Imap = require('imap'); const EventEmitter = require('events'); -const Promise = require('bluebird'); const Capabilities = { Gmail: 'X-GM-EXT-1', diff --git a/packages/nylas-core/index.js b/packages/nylas-core/index.js index 5365e0289..59733f444 100644 --- a/packages/nylas-core/index.js +++ b/packages/nylas-core/index.js @@ -1,7 +1,10 @@ +global.Promise = require('bluebird'); + module.exports = { DatabaseConnector: require('./database-connector'), PubsubConnector: require('./pubsub-connector'), IMAPConnection: require('./imap-connection'), SyncPolicy: require('./sync-policy'), + SchedulerUtils: require('./scheduler-utils'), Config: require(`./config/${process.env.ENV || 'development'}`), } diff --git a/packages/nylas-core/pubsub-connector.js b/packages/nylas-core/pubsub-connector.js index c13d01ccf..2022e0dca 100644 --- a/packages/nylas-core/pubsub-connector.js +++ b/packages/nylas-core/pubsub-connector.js @@ -1,11 +1,10 @@ const Rx = require('rx') -const bluebird = require('bluebird') const redis = require("redis"); const SyncPolicy = require('./sync-policy'); -bluebird.promisifyAll(redis.RedisClient.prototype); -bluebird.promisifyAll(redis.Multi.prototype); +Promise.promisifyAll(redis.RedisClient.prototype); +Promise.promisifyAll(redis.Multi.prototype); class PubsubConnector { constructor() { @@ -32,7 +31,7 @@ class PubsubConnector { } channelForAccountDeltas(accountId) { - return `a-${accountId}-deltas`; + return `deltas-${accountId}`; } // Shared channel diff --git a/packages/nylas-core/scheduler-utils.js b/packages/nylas-core/scheduler-utils.js new file mode 100644 index 000000000..f6595326d --- /dev/null +++ b/packages/nylas-core/scheduler-utils.js @@ -0,0 +1,29 @@ +const ACCOUNTS_UNCLAIMED = 'accounts:unclaimed'; +const ACCOUNTS_CLAIMED_PREFIX = 'accounts:id-'; +const ACCOUNTS_FOR = (id) => `${ACCOUNTS_CLAIMED_PREFIX}${id}`; +const HEARTBEAT_FOR = (id) => `heartbeat:${id}`; +const HEARTBEAT_EXPIRES = 30; // 2 min in prod? +const CLAIM_DURATION = 10 * 60 * 1000; // 2 hours on prod? + +const PubsubConnector = require('./pubsub-connector') + +const forEachAccountList = (forEachCallback) => { + const client = PubsubConnector.broadcastClient(); + return Promise.each(client.keysAsync(`accounts:*`), (key) => { + const processId = key.replace('accounts:', ''); + return client.lrangeAsync(key, 0, 20000).then((foundIds) => + forEachCallback(processId, foundIds) + ) + }); +} + +module.exports = { + ACCOUNTS_UNCLAIMED, + ACCOUNTS_CLAIMED_PREFIX, + ACCOUNTS_FOR, + HEARTBEAT_FOR, + HEARTBEAT_EXPIRES, + CLAIM_DURATION, + + forEachAccountList, +} diff --git a/packages/nylas-core/sync-policy.js b/packages/nylas-core/sync-policy.js index 5d2d59e0d..5443f3d28 100644 --- a/packages/nylas-core/sync-policy.js +++ b/packages/nylas-core/sync-policy.js @@ -1,7 +1,7 @@ class SyncPolicy { static defaultPolicy() { return { - afterSync: 'close', + afterSync: 'idle', interval: 120 * 1000, folderSyncOptions: { deepFolderScan: 10 * 60 * 1000, diff --git a/packages/nylas-dashboard/app.js b/packages/nylas-dashboard/app.js new file mode 100644 index 000000000..cb0b6c909 --- /dev/null +++ b/packages/nylas-dashboard/app.js @@ -0,0 +1,75 @@ +const Hapi = require('hapi'); +const HapiWebSocket = require('hapi-plugin-websocket'); +const Inert = require('inert'); +const {DatabaseConnector, PubsubConnector, SchedulerUtils} = require(`nylas-core`); +const {forEachAccountList} = SchedulerUtils; + +global.Promise = require('bluebird'); + +const server = new Hapi.Server(); +server.connection({ port: process.env.PORT || 5101 }); + +DatabaseConnector.forShared().then(({Account}) => { + server.register([HapiWebSocket, Inert], () => { + server.route({ + method: "POST", + path: "/accounts", + config: { + plugins: { + websocket: { + only: true, + connect: (wss, ws) => { + Account.findAll().then((accts) => { + accts.forEach((acct) => { + ws.send(JSON.stringify({ cmd: "ACCOUNT", payload: acct })); + }); + }); + this.redis = PubsubConnector.buildClient(); + this.redis.on('pmessage', (pattern, channel) => { + Account.find({where: {id: channel.replace('a-', '')}}).then((acct) => { + ws.send(JSON.stringify({ cmd: "ACCOUNT", payload: acct })); + }); + }); + this.redis.psubscribe(PubsubConnector.channelForAccount('*')); + this.assignmentsInterval = setInterval(() => { + const assignments = {}; + forEachAccountList((identity, accountIds) => { + for (const accountId of accountIds) { + assignments[accountId] = identity; + } + }).then(() => + ws.send(JSON.stringify({ cmd: "ASSIGNMENTS", payload: assignments})) + ) + }, 1000); + }, + disconnect: () => { + clearInterval(this.assignmentsInterval); + this.redis.quit(); + }, + }, + }, + }, + handler: (request, reply) => { + if (request.payload.cmd === "PING") { + reply(JSON.stringify({ result: "PONG" })); + return; + } + }, + }); + + server.route({ + method: 'GET', + path: '/{param*}', + handler: { + directory: { + path: 'public', + }, + }, + }); + + server.start((startErr) => { + if (startErr) { throw startErr; } + console.log('Server running at:', server.info.uri); + }); + }); +}); diff --git a/packages/nylas-dashboard/package.json b/packages/nylas-dashboard/package.json new file mode 100644 index 000000000..a05f0fc24 --- /dev/null +++ b/packages/nylas-dashboard/package.json @@ -0,0 +1,17 @@ +{ + "name": "nylas-dashboard", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "hapi": "^13.4.1", + "hapi-plugin-websocket": "^0.9.2", + "inert": "^4.0.0", + "nylas-core": "0.x.x" + } +} diff --git a/packages/nylas-dashboard/public/css/app.css b/packages/nylas-dashboard/public/css/app.css new file mode 100644 index 000000000..056b94d01 --- /dev/null +++ b/packages/nylas-dashboard/public/css/app.css @@ -0,0 +1,17 @@ +body { + background-image: -webkit-linear-gradient(top, rgba(232, 244, 250, 0.6), rgba(231, 231, 233, 1)), url(http://news.nationalgeographic.com/content/dam/news/2015/12/13/BookTalk%20K2/01BookTalkK2.jpg); + background-size: cover; + font-family: sans-serif; +} + +.account { + display:inline-block; + width: 300px; + height: 100px; + background-color: white; + padding:15px; +} + +.account h3 { + margin: 0; padding: 0; +} diff --git a/packages/nylas-dashboard/public/index.html b/packages/nylas-dashboard/public/index.html new file mode 100644 index 000000000..dac87884b --- /dev/null +++ b/packages/nylas-dashboard/public/index.html @@ -0,0 +1,13 @@ + +
+ + + + + + + +