IRC Federation: RFC2813 implementation (ngIRCd) (#10113)

* Initial progress

* Direct messaging complete

* Handle net-splits

* Cleaned up logging

* more cleanup, better log messages

* Added support for IRC users to create rooms and invite RC users

* Keep rooms in sync

* IRC user can kick RC user

* Working on transcription of coffescript to ecmascript code and fitting on the codebase.

* Adds settings section for config the IRC Server bridge.

* Working handles for direct messages

* Working handles for direct messages

* Working handles for direct messages

* Working handles for direct messages

* Working handles for direct messages

* Working on RC server connection to a local IRC Network

* first version, using a RFC2813 implementation

* Fixing lint errors

* Fixed partial username

* Fixed problems with scope

* removed parser name

* Added a button to reset the IRC connection

* Adjusted messages

* Fixed IRC federation for new users

* Ignore eslint about control character on regex

* Adjusted settings strings
pull/11187/head
Alan Sikora 8 years ago committed by Rodrigo Nascimento
parent 3ebb78d2f9
commit 17a63ec2ee
  1. 2
      .meteor/versions
  2. 1
      package.json
  3. 6
      packages/rocketchat-i18n/i18n/en.i18n.json
  4. 1
      packages/rocketchat-irc/.npm/package/.gitignore
  5. 7
      packages/rocketchat-irc/.npm/package/README
  6. 20
      packages/rocketchat-irc/.npm/package/npm-shrinkwrap.json
  7. 16
      packages/rocketchat-irc/package.js
  8. 138
      packages/rocketchat-irc/server/irc-bridge/index.js
  9. 9
      packages/rocketchat-irc/server/irc-bridge/localHandlers/index.js
  10. 15
      packages/rocketchat-irc/server/irc-bridge/localHandlers/onCreateRoom.js
  11. 32
      packages/rocketchat-irc/server/irc-bridge/localHandlers/onCreateUser.js
  12. 3
      packages/rocketchat-irc/server/irc-bridge/localHandlers/onJoinRoom.js
  13. 3
      packages/rocketchat-irc/server/irc-bridge/localHandlers/onLeaveRoom.js
  14. 32
      packages/rocketchat-irc/server/irc-bridge/localHandlers/onLogin.js
  15. 7
      packages/rocketchat-irc/server/irc-bridge/localHandlers/onLogout.js
  16. 32
      packages/rocketchat-irc/server/irc-bridge/localHandlers/onSaveMessage.js
  17. 13
      packages/rocketchat-irc/server/irc-bridge/peerHandlers/disconnected.js
  18. 8
      packages/rocketchat-irc/server/irc-bridge/peerHandlers/index.js
  19. 22
      packages/rocketchat-irc/server/irc-bridge/peerHandlers/joinedChannel.js
  20. 18
      packages/rocketchat-irc/server/irc-bridge/peerHandlers/leftChannel.js
  21. 19
      packages/rocketchat-irc/server/irc-bridge/peerHandlers/nickChanged.js
  22. 82
      packages/rocketchat-irc/server/irc-bridge/peerHandlers/sentMessage.js
  23. 42
      packages/rocketchat-irc/server/irc-bridge/peerHandlers/userRegistered.js
  24. 64
      packages/rocketchat-irc/server/irc-settings.js
  25. 24
      packages/rocketchat-irc/server/irc.js
  26. 52
      packages/rocketchat-irc/server/methods/resetIrcConnection.js
  27. 439
      packages/rocketchat-irc/server/server.js
  28. 519
      packages/rocketchat-irc/server/servers/RFC2813/codes.js
  29. 183
      packages/rocketchat-irc/server/servers/RFC2813/index.js
  30. 73
      packages/rocketchat-irc/server/servers/RFC2813/localCommandHandlers.js
  31. 69
      packages/rocketchat-irc/server/servers/RFC2813/parseMessage.js
  32. 112
      packages/rocketchat-irc/server/servers/RFC2813/peerCommandHandlers.js
  33. 3
      packages/rocketchat-irc/server/servers/index.js
  34. 78
      packages/rocketchat-irc/server/settings.js
  35. 3
      packages/rocketchat-lib/server/functions/createRoom.js
  36. 4
      packages/rocketchat-lib/server/models/Rooms.js
  37. 874
      packages/rocketchat-livechat/.app/package-lock.json

@ -171,7 +171,7 @@ rocketchat:importer-slack@0.0.1
rocketchat:importer-slack-users@1.0.0
rocketchat:integrations@0.0.1
rocketchat:internal-hubot@0.0.1
rocketchat:irc@0.0.2
rocketchat:irc@0.0.1
rocketchat:issuelinks@0.0.1
rocketchat:katex@0.0.1
rocketchat:lazy-load@0.0.1

@ -165,6 +165,7 @@
"poplib": "^0.1.7",
"prom-client": "^11.0.0",
"querystring": "^0.2.0",
"queue-fifo": "^0.2.4",
"redis": "^2.8.0",
"semver": "^5.5.0",
"sharp": "^0.20.3",

@ -538,6 +538,8 @@
"Condensed": "Condensed",
"Computer": "Computer",
"Confirm_password": "Confirm your password",
"Connection_Closed" : "Connection closed",
"Connection_Reset" : "Connection reset",
"Consulting": "Consulting",
"Consumer_Goods": "Consumer Goods",
"Contains_Security_Fixes": "Contains Security Fixes",
@ -1392,6 +1394,9 @@
"IRC_Channel_Users_End": "End of output of the NAMES command.",
"IRC_Description": "Internet Relay Chat (IRC) is a text-based group communication tool. Users join uniquely named channels, or rooms, for open discussion. IRC also supports private messages between individual users and file sharing capabilities. This package integrates these layers of functionality with Rocket.Chat.",
"IRC_Enabled": "Attempt to integrate IRC support. Changing this value requires restarting Rocket.Chat.",
"IRC_Enabled_Alert": "IRC Support is a work in progress. Use on a production system is not recommended at this time.",
"IRC_Federation": "IRC Federation",
"IRC_Federation_Disabled": "IRC Federation is disabled.",
"IRC_Hostname": "The IRC host server to connect to.",
"IRC_Login_Fail": "Output upon a failed connection to the IRC server.",
"IRC_Login_Success": "Output upon a successful connection to the IRC server.",
@ -2058,6 +2063,7 @@
"Reset": "Reset",
"Reset_password": "Reset password",
"Reset_section_settings": "Reset Section Settings",
"Reset_Connection" : "Reset Connection",
"Restart": "Restart",
"Restart_the_server": "Restart the server",
"Retail": "Retail",

@ -0,0 +1,7 @@
This directory and the files immediately inside it are automatically generated
when you change this package's NPM dependencies. Commit the files in this
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
so that others run the same versions of sub-dependencies.
You should NOT check in the node_modules directory that Meteor automatically
creates; if you are using git, the .gitignore file tells git to ignore it.

@ -0,0 +1,20 @@
{
"lockfileVersion": 1,
"dependencies": {
"dbly-linked-list": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/dbly-linked-list/-/dbly-linked-list-0.2.0.tgz",
"integrity": "sha512-Ool7y15f6JRDs0YKx7Dh9uiTb1jS1SZLNdT3Y2q16DlaEghXbMsmODS/XittjR2xztt1gJUpz7jVxpqAPF8VGg=="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
},
"queue-fifo": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/queue-fifo/-/queue-fifo-0.2.4.tgz",
"integrity": "sha512-o2xWptfzdw4QLIozUUcRPnppoTNK+X1DxWGd8csnJ1gQUsATfQaDryaGB1MhAu1L48vqPMtH69PZ1kZD82zlVw=="
}
}
}

@ -1,20 +1,22 @@
Package.describe({
name: 'rocketchat:irc',
version: '0.0.2',
summary: 'RocketChat libraries',
version: '0.0.1',
summary: 'RocketChat support for federating with IRC servers as a leaf node',
git: ''
});
Package.onUse(function(api) {
api.use([
'ecmascript',
'underscore',
'rocketchat:lib'
]);
api.addFiles([
'server/settings.js',
'server/server.js'
], 'server');
api.addFiles('server/irc.js', 'server');
api.addFiles('server/methods/resetIrcConnection.js', 'server');
api.addFiles('server/irc-settings.js', 'server');
});
api.export(['Irc'], ['server']);
Npm.depends({
'queue-fifo': '0.2.4'
});

@ -0,0 +1,138 @@
import Queue from 'queue-fifo';
import * as servers from '../servers';
import * as peerCommandHandlers from './peerHandlers';
import * as localCommandHandlers from './localHandlers';
class Bridge {
constructor(config) {
// General
this.config = config;
// Workaround for Rocket.Chat callbacks being called multiple times
this.loggedInUsers = [];
// Server
const Server = servers[this.config.server.protocol];
this.server = new Server(this.config);
this.setupPeerHandlers();
this.setupLocalHandlers();
// Command queue
this.queue = new Queue();
this.queueTimeout = 5;
}
init() {
this.loggedInUsers = [];
this.server.register();
this.server.on('registered', () => {
this.logQueue('Starting...');
this.runQueue();
});
}
stop() {
this.server.disconnect();
}
/**
* Log helper
*/
log(message) {
console.log(`[irc][bridge] ${ message }`);
}
logQueue(message) {
console.log(`[irc][bridge][queue] ${ message }`);
}
/**
*
*
* Queue
*
*
*/
onMessageReceived(from, command, ...parameters) {
this.queue.enqueue({ from, command, parameters });
}
async runQueue() {
// If it is empty, skip and keep the queue going
if (this.queue.isEmpty()) {
return setTimeout(this.runQueue.bind(this), this.queueTimeout);
}
// Get the command
const item = this.queue.dequeue();
this.logQueue(`Processing "${ item.command }" command from "${ item.from }"`);
// Handle the command accordingly
switch (item.from) {
case 'local':
if (!localCommandHandlers[item.command]) {
throw new Error(`Could not find handler for local:${ item.command }`);
}
await localCommandHandlers[item.command].apply(this, item.parameters);
break;
case 'peer':
if (!peerCommandHandlers[item.command]) {
throw new Error(`Could not find handler for peer:${ item.command }`);
}
await peerCommandHandlers[item.command].apply(this, item.parameters);
break;
}
// Keep the queue going
setTimeout(this.runQueue.bind(this), this.queueTimeout);
}
/**
*
*
* Peer
*
*
*/
setupPeerHandlers() {
this.server.on('peerCommand', (cmd) => {
this.onMessageReceived('peer', cmd.identifier, cmd.args);
});
}
/**
*
*
* Local
*
*
*/
setupLocalHandlers() {
// Auth
RocketChat.callbacks.add('afterValidateLogin', this.onMessageReceived.bind(this, 'local', 'onLogin'), RocketChat.callbacks.priority.LOW, 'irc-on-login');
RocketChat.callbacks.add('afterCreateUser', this.onMessageReceived.bind(this, 'local', 'onCreateUser'), RocketChat.callbacks.priority.LOW, 'irc-on-create-user');
// Joining rooms or channels
RocketChat.callbacks.add('afterCreateChannel', this.onMessageReceived.bind(this, 'local', 'onCreateRoom'), RocketChat.callbacks.priority.LOW, 'irc-on-create-channel');
RocketChat.callbacks.add('afterCreateRoom', this.onMessageReceived.bind(this, 'local', 'onCreateRoom'), RocketChat.callbacks.priority.LOW, 'irc-on-create-room');
RocketChat.callbacks.add('afterJoinRoom', this.onMessageReceived.bind(this, 'local', 'onJoinRoom'), RocketChat.callbacks.priority.LOW, 'irc-on-join-room');
// Leaving rooms or channels
RocketChat.callbacks.add('afterLeaveRoom', this.onMessageReceived.bind(this, 'local', 'onLeaveRoom'), RocketChat.callbacks.priority.LOW, 'irc-on-leave-room');
// Chatting
RocketChat.callbacks.add('afterSaveMessage', this.onMessageReceived.bind(this, 'local', 'onSaveMessage'), RocketChat.callbacks.priority.LOW, 'irc-on-save-message');
// Leaving
RocketChat.callbacks.add('afterLogoutCleanUp', this.onMessageReceived.bind(this, 'local', 'onLogout'), RocketChat.callbacks.priority.LOW, 'irc-on-logout');
}
sendCommand(command, parameters) {
this.server.emit('onReceiveFromLocal', command, parameters);
}
}
export default Bridge;

@ -0,0 +1,9 @@
import onCreateRoom from './onCreateRoom';
import onJoinRoom from './onJoinRoom';
import onLeaveRoom from './onLeaveRoom';
import onLogin from './onLogin';
import onLogout from './onLogout';
import onSaveMessage from './onSaveMessage';
import onCreateUser from './onCreateUser';
export { onCreateRoom, onJoinRoom, onLeaveRoom, onLogin, onLogout, onSaveMessage, onCreateUser };

@ -0,0 +1,15 @@
export default function handleOnCreateRoom(user, room) {
if (!room.usernames) {
return this.log(`Room ${ room.name } does not have a valid list of usernames`);
}
for (const username of room.usernames) {
const user = RocketChat.models.Users.findOne({ username });
if (user.profile.irc.fromIRC) {
this.sendCommand('joinChannel', { room, user });
} else {
this.sendCommand('joinedChannel', { room, user });
}
}
}

@ -0,0 +1,32 @@
export default function handleOnCreateUser(newUser) {
if (!newUser) {
return this.log('Invalid handleOnCreateUser call');
}
if (!newUser.username) {
return this.log('Invalid handleOnCreateUser call (Missing username)');
}
if (this.loggedInUsers.indexOf(newUser._id) !== -1) {
return this.log('Duplicate handleOnCreateUser call');
}
this.loggedInUsers.push(newUser._id);
Meteor.users.update({ _id: newUser._id }, {
$set: {
'profile.irc.fromIRC': false,
'profile.irc.username': `${ newUser.username }-rkt`,
'profile.irc.nick': `${ newUser.username }-rkt`,
'profile.irc.hostname': 'rocket.chat'
}
});
const user = RocketChat.models.Users.findOne({
_id: newUser._id
});
this.sendCommand('registerUser', user);
const rooms = RocketChat.models.Rooms.findWithUsername(user.username).fetch();
rooms.forEach(room => this.sendCommand('joinedChannel', { room, user }));
}

@ -0,0 +1,3 @@
export default function handleOnJoinRoom(user, room) {
this.sendCommand('joinedChannel', { room, user });
}

@ -0,0 +1,3 @@
export default function handleOnLeaveRoom(user, room) {
this.sendCommand('leftChannel', { room, user });
}

@ -0,0 +1,32 @@
export default function handleOnLogin(login) {
if (login.user === null) {
return this.log('Invalid handleOnLogin call');
}
if (!login.user.username) {
return this.log('Invalid handleOnLogin call (Missing username)');
}
if (this.loggedInUsers.indexOf(login.user._id) !== -1) {
return this.log('Duplicate handleOnLogin call');
}
this.loggedInUsers.push(login.user._id);
Meteor.users.update({ _id: login.user._id }, {
$set: {
'profile.irc.fromIRC': false,
'profile.irc.username': `${ login.user.username }-rkt`,
'profile.irc.nick': `${ login.user.username }-rkt`,
'profile.irc.hostname': 'rocket.chat'
}
});
const user = RocketChat.models.Users.findOne({
_id: login.user._id
});
this.sendCommand('registerUser', user);
const rooms = RocketChat.models.Rooms.findWithUsername(user.username).fetch();
rooms.forEach(room => this.sendCommand('joinedChannel', { room, user }));
}

@ -0,0 +1,7 @@
import _ from 'underscore';
export default function handleOnLogout(user) {
this.loggedInUsers = _.without(this.loggedInUsers, user._id);
this.sendCommand('disconnected', { user });
}

@ -0,0 +1,32 @@
export default function handleOnSaveMessage(message, to) {
let toIdentification = '';
// Direct message
if (to.t === 'd') {
const subscriptions = RocketChat.models.Subscriptions.findByRoomId(to._id);
subscriptions.forEach((subscription) => {
if (subscription.u.username !== to.username) {
const userData = RocketChat.models.Users.findOne({ username: subscription.u.username });
if (userData) {
if (userData.profile && userData.profile.irc && userData.profile.irc.nick) {
toIdentification = userData.profile.irc.nick;
} else {
toIdentification = userData.username;
}
} else {
toIdentification = subscription.u.username;
}
}
});
if (!toIdentification) {
console.error('[irc][server] Target user not found');
return;
}
} else {
toIdentification = `#${ to.name }`;
}
const user = RocketChat.models.Users.findOne({ _id: message.u._id });
this.sendCommand('sentMessage', { to: toIdentification, user, message: message.msg });
}

@ -0,0 +1,13 @@
export default function handleQUIT(args) {
const user = RocketChat.models.Users.findOne({
'profile.irc.nick': args.nick
});
Meteor.users.update({ _id: user._id }, {
$set: {
status: 'offline'
}
});
RocketChat.models.Rooms.removeUsernameFromAll(user.username);
}

@ -0,0 +1,8 @@
import disconnected from './disconnected';
import joinedChannel from './joinedChannel';
import leftChannel from './leftChannel';
import nickChanged from './nickChanged';
import sentMessage from './sentMessage';
import userRegistered from './userRegistered';
export { disconnected, joinedChannel, leftChannel, nickChanged, sentMessage, userRegistered };

@ -0,0 +1,22 @@
export default function handleJoinedChannel(args) {
const user = RocketChat.models.Users.findOne({
'profile.irc.nick': args.nick
});
if (!user) {
throw new Error(`Could not find a user with nick ${ args.nick }`);
}
let room = RocketChat.models.Rooms.findOneByName(args.roomName);
if (!room) {
const createdRoom = RocketChat.createRoom('c', args.roomName, user.username, [ /* usernames of the participants here */]);
room = RocketChat.models.Rooms.findOne({ _id: createdRoom.rid });
this.log(`${ user.username } created room ${ args.roomName }`);
} else {
RocketChat.addUserToRoom(room._id, user);
this.log(`${ user.username } joined room ${ room.name }`);
}
}

@ -0,0 +1,18 @@
export default function handleLeftChannel(args) {
const user = RocketChat.models.Users.findOne({
'profile.irc.nick': args.nick
});
if (!user) {
throw new Error(`Could not find a user with nick ${ args.nick }`);
}
const room = RocketChat.models.Rooms.findOneByName(args.roomName);
if (!room) {
throw new Error(`Could not find a room with name ${ args.roomName }`);
}
this.log(`${ user.username } left room ${ room.name }`);
RocketChat.removeUserFromRoom(room._id, user);
}

@ -0,0 +1,19 @@
export default function handleNickChanged(args) {
const user = RocketChat.models.Users.findOne({
'profile.irc.nick': args.nick
});
if (!user) {
throw new Error(`Could not find an user with nick ${ args.nick }`);
}
this.log(`${ user.username } changed nick: ${ args.nick } -> ${ args.newNick }`);
// Update on the database
RocketChat.models.Users.update({ _id: user._id }, {
$set: {
name: args.newNick,
'profile.irc.nick': args.newNick
}
});
}

@ -0,0 +1,82 @@
/*
*
* Get direct chat room helper
*
*
*/
const getDirectRoom = (source, target) => {
const rid = [ source._id, target._id ].sort().join('');
RocketChat.models.Rooms.upsert({ _id: rid }, {
$set: {
usernames: [source.username, target.username]
},
$setOnInsert: {
t: 'd',
msgs: 0,
ts: new Date()
}
});
RocketChat.models.Subscriptions.upsert({rid, 'u._id': target._id}, {
$setOnInsert: {
name: source.username,
t: 'd',
open: false,
alert: false,
unread: 0,
u: {
_id: target._id,
username: target.username
}
}
});
RocketChat.models.Subscriptions.upsert({rid, 'u._id': source._id}, {
$setOnInsert: {
name: target.username,
t: 'd',
open: false,
alert: false,
unread: 0,
u: {
_id: source._id,
username: source.username
}
}
});
return {
_id: rid,
t: 'd'
};
};
export default function handleSentMessage(args) {
const user = RocketChat.models.Users.findOne({
'profile.irc.nick': args.nick
});
if (!user) {
throw new Error(`Could not find a user with nick ${ args.nick }`);
}
let room;
if (args.roomName) {
room = RocketChat.models.Rooms.findOneByName(args.roomName);
} else {
const recipientUser = RocketChat.models.Users.findOne({
'profile.irc.nick': args.recipientNick
});
room = getDirectRoom(user, recipientUser);
}
const message = {
msg: args.message,
ts: new Date()
};
RocketChat.sendMessage(user, message, room);
}

@ -0,0 +1,42 @@
export default async function handleUserRegistered(args) {
// Check if there is an user with the given username
let user = RocketChat.models.Users.findOne({
'profile.irc.username': args.username
});
// If there is no user, create one...
if (!user) {
this.log(`Registering ${ args.username } with nick: ${ args.nick }`);
const userToInsert = {
name: args.nick,
username: `${ args.username }-irc`,
status: 'online',
utcOffset: 0,
active: true,
type: 'user',
profile: {
irc: {
fromIRC: true,
nick: args.nick,
username: args.username,
hostname: args.hostname
}
}
};
user = RocketChat.models.Users.create(userToInsert);
} else {
// ...otherwise, log the user in and update the information
this.log(`Logging in ${ args.username } with nick: ${ args.nick }`);
Meteor.users.update({ _id: user._id }, {
$set: {
status: 'online',
'profile.irc.nick': args.nick,
'profile.irc.username': args.username,
'profile.irc.hostname': args.hostname
}
});
}
}

@ -0,0 +1,64 @@
Meteor.startup(function() {
RocketChat.settings.addGroup('IRC_Federation', function() {
this.add('IRC_Enabled', false, {
type: 'boolean',
i18nLabel: 'Enabled',
i18nDescription: 'IRC_Enabled',
alert: 'IRC_Enabled_Alert'
});
this.add('IRC_Protocol', 'RFC2813', {
type: 'select',
i18nLabel: 'Protocol',
i18nDescription: 'IRC_Protocol',
values: [
{
key: 'RFC2813',
i18nLabel: 'RFC2813'
}
]
});
this.add('IRC_Host', 'localhost', {
type: 'string',
i18nLabel: 'Host',
i18nDescription: 'IRC_Host'
});
this.add('IRC_Port', 6667, {
type: 'int',
i18nLabel: 'Port',
i18nDescription: 'IRC_Port'
});
this.add('IRC_Name', 'irc.rocket.chat', {
type: 'string',
i18nLabel: 'Name',
i18nDescription: 'IRC_Name'
});
this.add('IRC_Description', 'Rocket.Chat IRC Bridge', {
type: 'string',
i18nLabel: 'Description',
i18nDescription: 'IRC_Description'
});
this.add('IRC_Local_Password', 'password', {
type: 'string',
i18nLabel: 'Local_Password',
i18nDescription: 'IRC_Local_Password'
});
this.add('IRC_Peer_Password', 'password', {
type: 'string',
i18nLabel: 'Peer_Password',
i18nDescription: 'IRC_Peer_Password'
});
this.add('IRC_Reset_Connection', 'resetIrcConnection', {
type: 'action',
actionText: 'Reset_Connection',
i18nLabel: 'Reset_Connection'
});
});
});

@ -0,0 +1,24 @@
import Bridge from './irc-bridge';
if (!!RocketChat.settings.get('IRC_Enabled') === true) {
// Normalize the config values
const config = {
server: {
protocol: RocketChat.settings.get('IRC_Protocol'),
host: RocketChat.settings.get('IRC_Host'),
port: RocketChat.settings.get('IRC_Port'),
name: RocketChat.settings.get('IRC_Name'),
description: RocketChat.settings.get('IRC_Description')
},
passwords: {
local: RocketChat.settings.get('IRC_Local_Password'),
peer: RocketChat.settings.get('IRC_Peer_Password')
}
};
Meteor.ircBridge = new Bridge(config);
Meteor.startup(() => {
Meteor.ircBridge.init();
});
}

@ -0,0 +1,52 @@
import Bridge from '../irc-bridge';
Meteor.methods({
resetIrcConnection() {
const ircEnabled = (!!RocketChat.settings.get('IRC_Enabled')) === true;
if (Meteor.ircBridge) {
Meteor.ircBridge.stop();
if (!ircEnabled) {
return {
message: 'Connection_Closed',
params: []
};
}
}
if (ircEnabled) {
if (Meteor.ircBridge) {
Meteor.ircBridge.init();
return {
message: 'Connection_Reset',
params: []
};
}
// Normalize the config values
const config = {
server: {
protocol: RocketChat.settings.get('IRC_Protocol'),
host: RocketChat.settings.get('IRC_Host'),
port: RocketChat.settings.get('IRC_Port'),
name: RocketChat.settings.get('IRC_Name'),
description: RocketChat.settings.get('IRC_Description')
},
passwords: {
local: RocketChat.settings.get('IRC_Local_Password'),
peer: RocketChat.settings.get('IRC_Peer_Password')
}
};
Meteor.ircBridge = new Bridge(config);
Meteor.ircBridge.init();
return {
message: 'Connection_Reset',
params: []
};
}
throw new Meteor.Error(t('IRC_Federation_Disabled'));
}
});

@ -1,439 +0,0 @@
import _ from 'underscore';
import net from 'net';
import Lru from 'lru-cache';
///////
// Assign values
//Package availability
const IRC_AVAILABILITY = RocketChat.settings.get('IRC_Enabled');
// Cache prep
const MESSAGE_CACHE_SIZE = RocketChat.settings.get('IRC_Message_Cache_Size');
const ircReceiveMessageCache = Lru(MESSAGE_CACHE_SIZE);//eslint-disable-line
const ircSendMessageCache = Lru(MESSAGE_CACHE_SIZE);//eslint-disable-line
// IRC server
const IRC_PORT = RocketChat.settings.get('IRC_Port');
const IRC_HOST = RocketChat.settings.get('IRC_Host');
const ircClientMap = {};
//////
// Core functionality
const bind = function(f) {
const g = Meteor.bindEnvironment((self, ...args) => f.apply(self, args));
return function(...args) { g(this, ...args); };
};
const async = (f, ...args) => Meteor.wrapAsync(f)(...args);
class IrcClient {
constructor(loginReq) {
this.loginReq = loginReq;
this.user = this.loginReq.user;
ircClientMap[this.user._id] = this;
this.ircPort = IRC_PORT;
this.ircHost = IRC_HOST;
this.msgBuf = [];
this.isConnected = false;
this.isDistroyed = false;
this.socket = new net.Socket;
this.socket.setNoDelay;
this.socket.setEncoding('utf-8');
this.socket.setKeepAlive(true);
this.connect = this.connect.bind(this);
this.onConnect = this.onConnect.bind(this);
this.onConnect = bind(this.onConnect);
this.onClose = bind(this.onClose);
this.onTimeout = bind(this.onTimeout);
this.onError = bind(this.onError);
this.onReceiveRawMessage = this.onReceiveRawMessage.bind(this);
this.onReceiveRawMessage = bind(this.onReceiveRawMessage);
this.socket.on('data', this.onReceiveRawMessage);
this.socket.on('close', this.onClose);
this.socket.on('timeout', this.onTimeout);
this.socket.on('error', this.onError);
this.isJoiningRoom = false;
this.receiveMemberListBuf = {};
this.pendingJoinRoomBuf = [];
this.successLoginMessageRegex = new RegExp(RocketChat.settings.get('IRC_RegEx_successLogin'));
this.failedLoginMessageRegex = new RegExp(RocketChat.settings.get('IRC_RegEx_failedLogin'));
this.receiveMessageRegex = new RegExp(RocketChat.settings.get('IRC_RegEx_receiveMessage'));
this.receiveMemberListRegex = new RegExp(RocketChat.settings.get('IRC_RegEx_receiveMemberList'));
this.endMemberListRegex = new RegExp(RocketChat.settings.get('IRC_RegEx_endMemberList'));
this.addMemberToRoomRegex = new RegExp(RocketChat.settings.get('IRC_RegEx_addMemberToRoom'));
this.removeMemberFromRoomRegex = new RegExp(RocketChat.settings.get('IRC_RegEx_removeMemberFromRoom'));
this.quitMemberRegex = new RegExp(RocketChat.settings.get('IRC_RegEx_quitMember'));
}
connect(loginCb) {
this.loginCb = loginCb;
this.socket.connect(this.ircPort, this.ircHost, this.onConnect);
this.initRoomList();
}
disconnect() {
this.isDistroyed = true;
this.socket.destroy();
}
onConnect() {
console.log('[irc] onConnect -> '.yellow, this.user.username, 'connect success.');
this.socket.write(`NICK ${ this.user.username }\r\n`);
this.socket.write(`USER ${ this.user.username } 0 * :${ this.user.name }\r\n`);
// message order could not make sure here
this.isConnected = true;
const messageBuf = this.msgBuf;
messageBuf.forEach(msg => this.socket.write(msg));
}
onClose() {
console.log('[irc] onClose -> '.yellow, this.user.username, 'connection close.');
this.isConnected = false;
if (this.isDistroyed) {
delete ircClientMap[this.user._id];
} else {
this.connect();
}
}
onTimeout() {
console.log('[irc] onTimeout -> '.yellow, this.user.username, 'connection timeout.', arguments);
}
onError() {
console.log('[irc] onError -> '.yellow, this.user.username, 'connection error.', arguments);
}
onReceiveRawMessage(data) {
data = data.toString().split('\n');
data.forEach(line => {
line = line.trim();
console.log(`[${ this.ircHost }:${ this.ircPort }]:`, line);
// Send heartbeat package to irc server
if (line.indexOf('PING') === 0) {
this.socket.write(line.replace('PING :', 'PONG '));
return;
}
let matchResult = this.receiveMessageRegex.exec(line);
if (matchResult) {
this.onReceiveMessage(matchResult[1], matchResult[2], matchResult[3]);
return;
}
matchResult = this.receiveMemberListRegex.exec(line);
if (matchResult) {
this.onReceiveMemberList(matchResult[1], matchResult[2].split(' '));
return;
}
matchResult = this.endMemberListRegex.exec(line);
if (matchResult) {
this.onEndMemberList(matchResult[1]);
return;
}
matchResult = this.addMemberToRoomRegex.exec(line);
if (matchResult) {
this.onAddMemberToRoom(matchResult[1], matchResult[2]);
return;
}
matchResult = this.removeMemberFromRoomRegex.exec(line);
if (matchResult) {
this.onRemoveMemberFromRoom(matchResult[1], matchResult[2]);
return;
}
matchResult = this.quitMemberRegex.exec(line);
if (matchResult) {
this.onQuitMember(matchResult[1]);
return;
}
matchResult = this.successLoginMessageRegex.exec(line);
if (matchResult) {
this.onSuccessLoginMessage();
return;
}
matchResult = this.failedLoginMessageRegex.exec(line);
if (matchResult) {
this.onFailedLoginMessage();
return;
}
});
}
onSuccessLoginMessage() {
console.log('[irc] onSuccessLoginMessage -> '.yellow);
if (this.loginCb) {
this.loginCb(null, this.loginReq);
}
}
onFailedLoginMessage() {
console.log('[irc] onFailedLoginMessage -> '.yellow);
this.loginReq.allowed = false;
this.disconnect();
if (this.loginCb) {
this.loginCb(null, this.loginReq);
}
}
onReceiveMessage(source, target, content) {
const now = new Date;
const timestamp = now.getTime();
let cacheKey = [source, target, content].join(',');
console.log('[irc] ircSendMessageCache.get -> '.yellow, 'key:', cacheKey, 'value:', ircSendMessageCache.get(cacheKey), 'ts:', timestamp - 1000);
if (ircSendMessageCache.get(cacheKey) > (timestamp - 1000)) {
return;
} else {
ircSendMessageCache.set(cacheKey, timestamp);
}
console.log('[irc] onReceiveMessage -> '.yellow, 'source:', source, 'target:', target, 'content:', content);
source = this.createUserWhenNotExist(source);
let room;
if (target[0] === '#') {
room = RocketChat.models.Rooms.findOneByName(target.substring(1));
} else {
room = this.createDirectRoomWhenNotExist(source, this.user);
}
const message = { msg: content, ts: now };
cacheKey = `${ source.username }${ timestamp }`;
ircReceiveMessageCache.set(cacheKey, true);
console.log('[irc] ircReceiveMessageCache.set -> '.yellow, 'key:', cacheKey);
RocketChat.sendMessage(source, message, room);
}
onReceiveMemberList(roomName, members) {
this.receiveMemberListBuf[roomName] = this.receiveMemberListBuf[roomName].concat(members);
}
onEndMemberList(roomName) {
const newMembers = this.receiveMemberListBuf[roomName];
console.log('[irc] onEndMemberList -> '.yellow, 'room:', roomName, 'members:', newMembers.join(','));
const room = RocketChat.models.Rooms.findOneByNameAndType(roomName, 'c');
if (!room) {
return;
}
const oldMembers = room.usernames;
const appendMembers = _.difference(newMembers, oldMembers);
const removeMembers = _.difference(oldMembers, newMembers);
appendMembers.forEach(member => this.createUserWhenNotExist(member));
RocketChat.models.Rooms.removeUsernamesById(room._id, removeMembers);
RocketChat.models.Rooms.addUsernamesById(room._id, appendMembers);
this.isJoiningRoom = false;
roomName = this.pendingJoinRoomBuf.shift();
if (roomName) {
this.joinRoom({
t: 'c',
name: roomName
});
}
}
sendRawMessage(msg) {
console.log('[irc] sendRawMessage -> '.yellow, msg.slice(0, -2));
if (this.isConnected) {
this.socket.write(msg);
} else {
this.msgBuf.push(msg);
}
}
sendMessage(room, message) {
console.log('[irc] sendMessage -> '.yellow, 'userName:', message.u.username);
let target = '';
if (room.t === 'c') {
target = `#${ room.name }`;
} else if (room.t === 'd') {
const usernames = room.usernames;
usernames.forEach(name => {
if (message.u.username !== name) {
target = name;
return;
}
});
}
const cacheKey = [this.user.username, target, message.msg].join(',');
console.log('[irc] ircSendMessageCache.set -> '.yellow, 'key:', cacheKey, 'ts:', message.ts.getTime());
ircSendMessageCache.set(cacheKey, message.ts.getTime());
const msg = `PRIVMSG ${ target } :${ message.msg }\r\n`;
this.sendRawMessage(msg);
}
initRoomList() {
const roomsCursor = RocketChat.models.Rooms.findByTypeContainingUsername('c', this.user.username, { fields: { name: 1, t: 1 }});
const rooms = roomsCursor.fetch();
rooms.forEach(room => this.joinRoom(room));
}
joinRoom(room) {
if (room.t !== 'c' || room.name === 'general') {
return;
}
if (this.isJoiningRoom) {
return this.pendingJoinRoomBuf.push(room.name);
}
console.log('[irc] joinRoom -> '.yellow, 'roomName:', room.name, 'pendingJoinRoomBuf:', this.pendingJoinRoomBuf.join(','));
const msg = `JOIN #${ room.name }\r\n`;
this.receiveMemberListBuf[room.name] = [];
this.sendRawMessage(msg);
this.isJoiningRoom = true;
}
leaveRoom(room) {
if (room.t !== 'c') {
return;
}
const msg = `PART #${ room.name }\r\n`;
this.sendRawMessage(msg);
}
getMemberList(room) {
if (room.t !== 'c') {
return;
}
const msg = `NAMES #${ room.name }\r\n`;
this.receiveMemberListBuf[room.name] = [];
this.sendRawMessage(msg);
}
onAddMemberToRoom(member, roomName) {
if (this.user.username === member) {
return;
}
console.log('[irc] onAddMemberToRoom -> '.yellow, 'roomName:', roomName, 'member:', member);
this.createUserWhenNotExist(member);
RocketChat.models.Rooms.addUsernameByName(roomName, member);
}
onRemoveMemberFromRoom(member, roomName) {
console.log('[irc] onRemoveMemberFromRoom -> '.yellow, 'roomName:', roomName, 'member:', member);
RocketChat.models.Rooms.removeUsernameByName(roomName, member);
}
onQuitMember(member) {
console.log('[irc] onQuitMember ->'.yellow, 'username:', member);
RocketChat.models.Rooms.removeUsernameFromAll(member);
Meteor.users.update({ name: member }, { $set: { status: 'offline' }});
}
createUserWhenNotExist(name) {
const user = Meteor.users.findOne({ name });
if (user) {
return user;
}
console.log('[irc] createNotExistUser ->'.yellow, 'userName:', name);
Meteor.call('registerUser', {
email: `${ name }@rocketchat.org`,
pass: 'rocketchat',
name
});
Meteor.users.update({ name }, {
$set: {
status: 'online',
username: name
}
});
return Meteor.users.findOne({ name });
}
createDirectRoomWhenNotExist(source, target) {
console.log('[irc] createDirectRoomWhenNotExist -> '.yellow, 'source:', source, 'target:', target);
const rid = [source._id, target._id].sort().join('');
const now = new Date();
RocketChat.models.Rooms.upsert({ _id: rid}, {
$set: {
usernames: [source.username, target.username]
},
$setOnInsert: {
t: 'd',
msgs: 0,
ts: now
}
});
RocketChat.models.Subscriptions.upsert({ rid, $and: [{ 'u._id': target._id}]}, {
$setOnInsert: {
name: source.username,
t: 'd',
open: false,
alert: false,
unread: 0,
userMentions: 0,
groupMentions: 0,
u: { _id: target._id, username: target.username }}
});
return { t: 'd', _id: rid };
}
}
IrcClient.getByUid = function(uid) {
return ircClientMap[uid];
};
IrcClient.create = function(login) {
if (login.user == null) {
return login;
}
if (!(login.user._id in ircClientMap)) {
const ircClient = new IrcClient(login);
return async(ircClient.connect);
}
return login;
};
function IrcLoginer(login) {
console.log('[irc] validateLogin -> '.yellow, login);
return IrcClient.create(login);
}
function IrcSender(message) {
const name = message.u.username;
const timestamp = message.ts.getTime();
const cacheKey = `${ name }${ timestamp }`;
if (ircReceiveMessageCache.get(cacheKey)) {
return message;
}
const room = RocketChat.models.Rooms.findOneById(message.rid, { fields: { name: 1, usernames: 1, t: 1 }});
const ircClient = IrcClient.getByUid(message.u._id);
ircClient.sendMessage(room, message);
return message;
}
function IrcRoomJoiner(user, room) {
const ircClient = IrcClient.getByUid(user._id);
ircClient.joinRoom(room);
return room;
}
function IrcRoomLeaver(user, room) {
const ircClient = IrcClient.getByUid(user._id);
ircClient.leaveRoom(room);
return room;
}
function IrcLogoutCleanUper(user) {
const ircClient = IrcClient.getByUid(user._id);
ircClient.disconnect();
return user;
}
//////
// Make magic happen
// Only proceed if the package has been enabled
if (IRC_AVAILABILITY === true) {
RocketChat.callbacks.add('beforeValidateLogin', IrcLoginer, RocketChat.callbacks.priority.LOW, 'irc-loginer');
RocketChat.callbacks.add('beforeSaveMessage', IrcSender, RocketChat.callbacks.priority.LOW, 'irc-sender');
RocketChat.callbacks.add('beforeJoinRoom', IrcRoomJoiner, RocketChat.callbacks.priority.LOW, 'irc-room-joiner');
RocketChat.callbacks.add('beforeCreateChannel', IrcRoomJoiner, RocketChat.callbacks.priority.LOW, 'irc-room-joiner-create-channel');
RocketChat.callbacks.add('beforeLeaveRoom', IrcRoomLeaver, RocketChat.callbacks.priority.LOW, 'irc-room-leaver');
RocketChat.callbacks.add('afterLogoutCleanUp', IrcLogoutCleanUper, RocketChat.callbacks.priority.LOW, 'irc-clean-up');
}

@ -0,0 +1,519 @@
/**
* This file is part of https://github.com/martynsmith/node-irc
* by https://github.com/martynsmith
*/
module.exports = {
'001': {
name: 'rpl_welcome',
type: 'reply'
},
'002': {
name: 'rpl_yourhost',
type: 'reply'
},
'003': {
name: 'rpl_created',
type: 'reply'
},
'004': {
name: 'rpl_myinfo',
type: 'reply'
},
'005': {
name: 'rpl_isupport',
type: 'reply'
},
200: {
name: 'rpl_tracelink',
type: 'reply'
},
201: {
name: 'rpl_traceconnecting',
type: 'reply'
},
202: {
name: 'rpl_tracehandshake',
type: 'reply'
},
203: {
name: 'rpl_traceunknown',
type: 'reply'
},
204: {
name: 'rpl_traceoperator',
type: 'reply'
},
205: {
name: 'rpl_traceuser',
type: 'reply'
},
206: {
name: 'rpl_traceserver',
type: 'reply'
},
208: {
name: 'rpl_tracenewtype',
type: 'reply'
},
211: {
name: 'rpl_statslinkinfo',
type: 'reply'
},
212: {
name: 'rpl_statscommands',
type: 'reply'
},
213: {
name: 'rpl_statscline',
type: 'reply'
},
214: {
name: 'rpl_statsnline',
type: 'reply'
},
215: {
name: 'rpl_statsiline',
type: 'reply'
},
216: {
name: 'rpl_statskline',
type: 'reply'
},
218: {
name: 'rpl_statsyline',
type: 'reply'
},
219: {
name: 'rpl_endofstats',
type: 'reply'
},
221: {
name: 'rpl_umodeis',
type: 'reply'
},
241: {
name: 'rpl_statslline',
type: 'reply'
},
242: {
name: 'rpl_statsuptime',
type: 'reply'
},
243: {
name: 'rpl_statsoline',
type: 'reply'
},
244: {
name: 'rpl_statshline',
type: 'reply'
},
250: {
name: 'rpl_statsconn',
type: 'reply'
},
251: {
name: 'rpl_luserclient',
type: 'reply'
},
252: {
name: 'rpl_luserop',
type: 'reply'
},
253: {
name: 'rpl_luserunknown',
type: 'reply'
},
254: {
name: 'rpl_luserchannels',
type: 'reply'
},
255: {
name: 'rpl_luserme',
type: 'reply'
},
256: {
name: 'rpl_adminme',
type: 'reply'
},
257: {
name: 'rpl_adminloc1',
type: 'reply'
},
258: {
name: 'rpl_adminloc2',
type: 'reply'
},
259: {
name: 'rpl_adminemail',
type: 'reply'
},
261: {
name: 'rpl_tracelog',
type: 'reply'
},
265: {
name: 'rpl_localusers',
type: 'reply'
},
266: {
name: 'rpl_globalusers',
type: 'reply'
},
300: {
name: 'rpl_none',
type: 'reply'
},
301: {
name: 'rpl_away',
type: 'reply'
},
302: {
name: 'rpl_userhost',
type: 'reply'
},
303: {
name: 'rpl_ison',
type: 'reply'
},
305: {
name: 'rpl_unaway',
type: 'reply'
},
306: {
name: 'rpl_nowaway',
type: 'reply'
},
311: {
name: 'rpl_whoisuser',
type: 'reply'
},
312: {
name: 'rpl_whoisserver',
type: 'reply'
},
313: {
name: 'rpl_whoisoperator',
type: 'reply'
},
314: {
name: 'rpl_whowasuser',
type: 'reply'
},
315: {
name: 'rpl_endofwho',
type: 'reply'
},
317: {
name: 'rpl_whoisidle',
type: 'reply'
},
318: {
name: 'rpl_endofwhois',
type: 'reply'
},
319: {
name: 'rpl_whoischannels',
type: 'reply'
},
321: {
name: 'rpl_liststart',
type: 'reply'
},
322: {
name: 'rpl_list',
type: 'reply'
},
323: {
name: 'rpl_listend',
type: 'reply'
},
324: {
name: 'rpl_channelmodeis',
type: 'reply'
},
329: {
name: 'rpl_creationtime',
type: 'reply'
},
331: {
name: 'rpl_notopic',
type: 'reply'
},
332: {
name: 'rpl_topic',
type: 'reply'
},
333: {
name: 'rpl_topicwhotime',
type: 'reply'
},
341: {
name: 'rpl_inviting',
type: 'reply'
},
342: {
name: 'rpl_summoning',
type: 'reply'
},
351: {
name: 'rpl_version',
type: 'reply'
},
352: {
name: 'rpl_whoreply',
type: 'reply'
},
353: {
name: 'rpl_namreply',
type: 'reply'
},
364: {
name: 'rpl_links',
type: 'reply'
},
365: {
name: 'rpl_endoflinks',
type: 'reply'
},
366: {
name: 'rpl_endofnames',
type: 'reply'
},
367: {
name: 'rpl_banlist',
type: 'reply'
},
368: {
name: 'rpl_endofbanlist',
type: 'reply'
},
369: {
name: 'rpl_endofwhowas',
type: 'reply'
},
371: {
name: 'rpl_info',
type: 'reply'
},
372: {
name: 'rpl_motd',
type: 'reply'
},
374: {
name: 'rpl_endofinfo',
type: 'reply'
},
375: {
name: 'rpl_motdstart',
type: 'reply'
},
376: {
name: 'rpl_endofmotd',
type: 'reply'
},
381: {
name: 'rpl_youreoper',
type: 'reply'
},
382: {
name: 'rpl_rehashing',
type: 'reply'
},
391: {
name: 'rpl_time',
type: 'reply'
},
392: {
name: 'rpl_usersstart',
type: 'reply'
},
393: {
name: 'rpl_users',
type: 'reply'
},
394: {
name: 'rpl_endofusers',
type: 'reply'
},
395: {
name: 'rpl_nousers',
type: 'reply'
},
401: {
name: 'err_nosuchnick',
type: 'error'
},
402: {
name: 'err_nosuchserver',
type: 'error'
},
403: {
name: 'err_nosuchchannel',
type: 'error'
},
404: {
name: 'err_cannotsendtochan',
type: 'error'
},
405: {
name: 'err_toomanychannels',
type: 'error'
},
406: {
name: 'err_wasnosuchnick',
type: 'error'
},
407: {
name: 'err_toomanytargets',
type: 'error'
},
409: {
name: 'err_noorigin',
type: 'error'
},
411: {
name: 'err_norecipient',
type: 'error'
},
412: {
name: 'err_notexttosend',
type: 'error'
},
413: {
name: 'err_notoplevel',
type: 'error'
},
414: {
name: 'err_wildtoplevel',
type: 'error'
},
421: {
name: 'err_unknowncommand',
type: 'error'
},
422: {
name: 'err_nomotd',
type: 'error'
},
423: {
name: 'err_noadmininfo',
type: 'error'
},
424: {
name: 'err_fileerror',
type: 'error'
},
431: {
name: 'err_nonicknamegiven',
type: 'error'
},
432: {
name: 'err_erroneusnickname',
type: 'error'
},
433: {
name: 'err_nicknameinuse',
type: 'error'
},
436: {
name: 'err_nickcollision',
type: 'error'
},
441: {
name: 'err_usernotinchannel',
type: 'error'
},
442: {
name: 'err_notonchannel',
type: 'error'
},
443: {
name: 'err_useronchannel',
type: 'error'
},
444: {
name: 'err_nologin',
type: 'error'
},
445: {
name: 'err_summondisabled',
type: 'error'
},
446: {
name: 'err_usersdisabled',
type: 'error'
},
451: {
name: 'err_notregistered',
type: 'error'
},
461: {
name: 'err_needmoreparams',
type: 'error'
},
462: {
name: 'err_alreadyregistred',
type: 'error'
},
463: {
name: 'err_nopermforhost',
type: 'error'
},
464: {
name: 'err_passwdmismatch',
type: 'error'
},
465: {
name: 'err_yourebannedcreep',
type: 'error'
},
467: {
name: 'err_keyset',
type: 'error'
},
471: {
name: 'err_channelisfull',
type: 'error'
},
472: {
name: 'err_unknownmode',
type: 'error'
},
473: {
name: 'err_inviteonlychan',
type: 'error'
},
474: {
name: 'err_bannedfromchan',
type: 'error'
},
475: {
name: 'err_badchannelkey',
type: 'error'
},
481: {
name: 'err_noprivileges',
type: 'error'
},
482: {
name: 'err_chanoprivsneeded',
type: 'error'
},
483: {
name: 'err_cantkillserver',
type: 'error'
},
491: {
name: 'err_nooperhost',
type: 'error'
},
501: {
name: 'err_umodeunknownflag',
type: 'error'
},
502: {
name: 'err_usersdontmatch',
type: 'error'
}
};

@ -0,0 +1,183 @@
import net from 'net';
import util from 'util';
import { EventEmitter } from 'events';
import parseMessage from './parseMessage';
import peerCommandHandlers from './peerCommandHandlers';
import localCommandHandlers from './localCommandHandlers';
class RFC2813 {
constructor(config) {
this.config = config;
// Hold registered state
this.registerSteps = [];
this.isRegistered = false;
// Hold peer server information
this.serverPrefix = null;
// Hold the buffer while receiving
this.receiveBuffer = new Buffer('');
}
/**
* Setup socket
*/
setupSocket() {
// Setup socket
this.socket = new net.Socket();
this.socket.setNoDelay();
this.socket.setEncoding('utf-8');
this.socket.setKeepAlive(true);
this.socket.setTimeout(90000);
this.socket.on('data', this.onReceiveFromPeer.bind(this));
this.socket.on('connect', this.onConnect.bind(this));
this.socket.on('error', (err) => console.log('[irc][server][err]', err));
this.socket.on('timeout', () => this.log('Timeout'));
this.socket.on('close', () => this.log('Connection Closed'));
// Setup local
this.on('onReceiveFromLocal', this.onReceiveFromLocal.bind(this));
}
/**
* Log helper
*/
log(message) {
console.log(`[irc][server] ${ message }`);
}
/**
* Connect
*/
register() {
this.log(`Connecting to @${ this.config.server.host }:${ this.config.server.port }`);
if (!this.socket) {
this.setupSocket();
}
this.socket.connect(this.config.server.port, this.config.server.host);
}
/**
* Disconnect
*/
disconnect() {
this.log('Disconnecting from server.');
if (this.socket) {
this.socket.destroy();
this.socket = undefined;
}
this.isRegistered = false;
this.registerSteps = [];
}
/**
* Setup the server connection
*/
onConnect() {
this.log('Connected! Registering as server...');
this.write({
command: 'PASS',
parameters: [ this.config.passwords.local, '0210', 'ngircd' ]
});
this.write({
command: 'SERVER', parameters: [ this.config.server.name ],
trailer: this.config.server.description
});
}
/**
* Sends a command message through the socket
*/
write(command) {
let buffer = command.prefix ? `:${ command.prefix } ` : '';
buffer += command.command;
if (command.parameters && command.parameters.length > 0) {
buffer += ` ${ command.parameters.join(' ') }`;
}
if (command.trailer) {
buffer += ` :${ command.trailer }`;
}
this.log(`Sending Command: ${ buffer }`);
return this.socket.write(`${ buffer }\r\n`);
}
/**
*
*
* Peer message handling
*
*
*/
onReceiveFromPeer(chunk) {
if (typeof (chunk) === 'string') {
this.receiveBuffer += chunk;
} else {
this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk]);
}
const lines = this.receiveBuffer.toString().split(/\r\n|\r|\n|\u0007/); // eslint-disable-line no-control-regex
// If the buffer does not end with \r\n, more chunks are coming
if (lines.pop()) {
return;
}
// Reset the buffer
this.receiveBuffer = new Buffer('');
lines.forEach((line) => {
if (line.length && !line.startsWith('\a')) {
const parsedMessage = parseMessage(line);
if (peerCommandHandlers[parsedMessage.command]) {
this.log(`Handling peer message: ${ line }`);
const command = peerCommandHandlers[parsedMessage.command].call(this, parsedMessage);
if (command) {
this.log(`Emitting peer command to local: ${ JSON.stringify(command) }`);
this.emit('peerCommand', command);
}
} else {
this.log(`Unhandled peer message: ${ JSON.stringify(parsedMessage) }`);
}
}
});
}
/**
*
*
* Local message handling
*
*
*/
onReceiveFromLocal(command, parameters) {
if (localCommandHandlers[command]) {
this.log(`Handling local command: ${ command }`);
localCommandHandlers[command].call(this, parameters);
} else {
this.log(`Unhandled local command: ${ JSON.stringify(command) }`);
}
}
}
util.inherits(RFC2813, EventEmitter);
export default RFC2813;

@ -0,0 +1,73 @@
function registerUser(parameters) {
const { name, profile: { irc: { nick, username } } } = parameters;
this.write({
prefix: this.config.server.name,
command: 'NICK', parameters: [ nick, 1, username, 'irc.rocket.chat', 1, '+i' ],
trailer: name
});
}
function joinChannel(parameters) {
const {
room: { name: roomName },
user: { profile: { irc: { nick } } }
} = parameters;
this.write({
prefix: this.config.server.name,
command: 'NJOIN', parameters: [ `#${ roomName }` ],
trailer: nick
});
}
function joinedChannel(parameters) {
const {
room: { name: roomName },
user: { profile: { irc: { nick } } }
} = parameters;
this.write({
prefix: nick,
command: 'JOIN', parameters: [ `#${ roomName }` ]
});
}
function leftChannel(parameters) {
const {
room: { name: roomName },
user: { profile: { irc: { nick } } }
} = parameters;
this.write({
prefix: nick,
command: 'PART', parameters: [ `#${ roomName }` ]
});
}
function sentMessage(parameters) {
const {
user: { profile: { irc: { nick } } },
to,
message
} = parameters;
this.write({
prefix: nick,
command: 'PRIVMSG', parameters: [ to ],
trailer: message
});
}
function disconnected(parameters) {
const {
user: { profile: { irc: { nick } } }
} = parameters;
this.write({
prefix: nick,
command: 'QUIT'
});
}
export default { registerUser, joinChannel, joinedChannel, leftChannel, sentMessage, disconnected };

@ -0,0 +1,69 @@
/**
* This file is part of https://github.com/martynsmith/node-irc
* by https://github.com/martynsmith
*/
const replyFor = require('./codes');
/**
* parseMessage(line, stripColors)
*
* takes a raw "line" from the IRC server and turns it into an object with
* useful keys
* @param {String} line Raw message from IRC server.
* @return {Object} A parsed message object.
*/
module.exports = function parseMessage(line) {
const message = {};
let match;
// Parse prefix
match = line.match(/^:([^ ]+) +/);
if (match) {
message.prefix = match[1];
line = line.replace(/^:[^ ]+ +/, '');
match = message.prefix.match(/^([_a-zA-Z0-9\~\[\]\\`^{}|-]*)(!([^@]+)@(.*))?$/);
if (match) {
message.nick = match[1];
message.user = match[3];
message.host = match[4];
} else {
message.server = message.prefix;
}
}
// Parse command
match = line.match(/^([^ ]+) */);
message.command = match[1];
message.rawCommand = match[1];
message.commandType = 'normal';
line = line.replace(/^[^ ]+ +/, '');
if (replyFor[message.rawCommand]) {
message.command = replyFor[message.rawCommand].name;
message.commandType = replyFor[message.rawCommand].type;
}
message.args = [];
let middle;
let trailing;
// Parse parameters
if (line.search(/^:|\s+:/) !== -1) {
match = line.match(/(.*?)(?:^:|\s+:)(.*)/);
middle = match[1].trimRight();
trailing = match[2];
} else {
middle = line;
}
if (middle.length) {
message.args = middle.split(/ +/);
}
if (typeof (trailing) !== 'undefined' && trailing.length) {
message.args.push(trailing);
}
return message;
};

@ -0,0 +1,112 @@
function PASS() {
this.log('Received PASS command, continue registering...');
this.registerSteps.push('PASS');
}
function SERVER(parsedMessage) {
this.log('Received SERVER command, waiting for first PING...');
this.serverPrefix = parsedMessage.prefix;
this.registerSteps.push('SERVER');
}
function PING() {
if (!this.isRegistered && this.registerSteps.length === 2) {
this.log('Received first PING command, server is registered!');
this.isRegistered = true;
this.emit('registered');
}
this.write({
prefix: this.config.server.name,
command: 'PONG',
parameters: [ this.config.server.name ]
});
}
function NICK(parsedMessage) {
let command;
// Check if the message comes from the server,
// which means it is a new user
if (parsedMessage.prefix === this.serverPrefix) {
command = {
identifier: 'userRegistered',
args: {
nick: parsedMessage.args[0],
username: parsedMessage.args[2],
host: parsedMessage.args[3],
name: parsedMessage.args[6]
}
};
} else { // Otherwise, it is a nick change
command = {
identifier: 'nickChanged',
args: {
nick: parsedMessage.nick,
newNick: parsedMessage.args[0]
}
};
}
return command;
}
function JOIN(parsedMessage) {
const command = {
identifier: 'joinedChannel',
args: {
roomName: parsedMessage.args[0].substring(1),
nick: parsedMessage.prefix
}
};
return command;
}
function PART(parsedMessage) {
const command = {
identifier: 'leftChannel',
args: {
roomName: parsedMessage.args[0].substring(1),
nick: parsedMessage.prefix
}
};
return command;
}
function PRIVMSG(parsedMessage) {
const command = {
identifier: 'sentMessage',
args: {
nick: parsedMessage.prefix,
message: parsedMessage.args[1]
}
};
if (parsedMessage.args[0][0] === '#') {
command.args.roomName = parsedMessage.args[0].substring(1);
} else {
command.args.recipientNick = parsedMessage.args[0];
}
return command;
}
function QUIT(parsedMessage) {
const command = {
identifier: 'disconnected',
args: {
nick: parsedMessage.prefix
}
};
return command;
}
export default { PASS, SERVER, PING, NICK, JOIN, PART, PRIVMSG, QUIT };

@ -0,0 +1,3 @@
import RFC2813 from './RFC2813';
export { RFC2813 };

@ -1,78 +0,0 @@
Meteor.startup(function() {
RocketChat.settings.addGroup('IRC', function() {
// Is this thing on?
this.add('IRC_Enabled', false, {
type: 'boolean',
i18nLabel: 'Enabled',
i18nDescription: 'IRC_Enabled',
alert: 'IRC Support is a work in progress. Use on a production system is not recommended at this time.'
});
// The IRC host server to talk to
this.add('IRC_Host', 'irc.freenode.net', {
type: 'string',
i18nLabel: 'Host',
i18nDescription: 'IRC_Hostname'
});
// The port to connect on the remote server
this.add('IRC_Port', 6667, {
type: 'int',
i18nLabel: 'Port',
i18nDescription: 'IRC_Port'
});
// Cache size of the messages we send the host IRC server
this.add('IRC_Message_Cache_Size', 200, {
type: 'int',
i18nLabel: 'Message Cache Size',
i18nDescription: 'IRC_Message_Cache_Size'
});
// Expandable box for modifying regular expressions for IRC interaction
this.section('Regular_Expressions', function() {
this.add('IRC_RegEx_successLogin', 'Welcome to the freenode Internet Relay Chat Network', {
type: 'string',
i18nLabel: 'Login Successful',
i18nDescription: 'IRC_Login_Success'
});
this.add('IRC_RegEx_failedLogin', 'You have not registered', {
type: 'string',
i18nLabel: 'Login Failed',
i18nDescription: 'IRC_Login_Fail'
});
this.add('IRC_RegEx_receiveMessage', '^:(\S+)!~\S+ PRIVMSG (\S+) :(.+)$', {
type: 'string',
i18nLabel: 'Private Message',
i18nDescription: 'IRC_Private_Message'
});
this.add('IRC_RegEx_receiveMemberList', '^:\S+ \d+ \S+ = #(\S+) :(.*)$', {
type: 'string',
i18nLabel: 'Channel User List Start',
i18nDescription: 'IRC_Channel_Users'
});
this.add('IRC_RegEx_endMemberList', '^.+#(\S+) :End of \/NAMES list.$', {
type: 'string',
i18nLabel: 'Channel User List End',
i18nDescription: 'IRC_Channel_Users_End'
});
this.add('IRC_RegEx_addMemberToRoom', '^:(\S+)!~\S+ JOIN #(\S+)$', {
type: 'string',
i18nLabel: 'Join Channel',
i18nDescription: 'IRC_Channel_Join'
});
this.add('IRC_RegEx_removeMemberFromRoom', '^:(\S+)!~\S+ PART #(\S+)$', {
type: 'string',
i18nLabel: 'Leave Channel',
i18nDescription: 'IRC_Channel_Leave'
});
this.add('IRC_RegEx_quitMember', '^:(\S+)!~\S+ QUIT .*$', {
type: 'string',
i18nLabel: 'Quit IRC Session',
i18nDescription: 'IRC_Quit'
});
});
});
});

@ -95,6 +95,9 @@ RocketChat.createRoom = function(type, name, owner, members, readOnly, extraData
RocketChat.callbacks.run('afterCreatePrivateGroup', owner, room);
});
}
Meteor.defer(() => {
RocketChat.callbacks.run('afterCreateRoom', owner, room);
});
if (Apps && Apps.isLoaded()) {
// This returns a promise, but it won't mutate anything about the message

@ -88,6 +88,10 @@ class ModelRooms extends RocketChat.models._Base {
// FIND
findWithUsername(username, options) {
return this.find({ usernames: username }, options);
}
findById(roomId, options) {
return this.find({ _id: roomId }, options);
}

@ -0,0 +1,874 @@
{
"name": "rocketchat-livechat",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"ajv": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
"requires": {
"co": "4.6.0",
"fast-deep-equal": "1.0.0",
"fast-json-stable-stringify": "2.0.0",
"json-schema-traverse": "0.3.1"
}
},
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"are-we-there-yet": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz",
"integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=",
"requires": {
"delegates": "1.0.0",
"readable-stream": "2.3.4"
}
},
"asn1": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz",
"integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y="
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"autolinker": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/autolinker/-/autolinker-1.6.2.tgz",
"integrity": "sha512-IKLGtYFb3jzGTtgCpb4bm//1sXmmmgmr0msKshhYoc7EsWmLCFvuyxLcEIfcZ5gbCgZGXrnXkOkcBblOFEnlog=="
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz",
"integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4="
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"requires": {
"core-js": "2.5.3",
"regenerator-runtime": "0.11.1"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"bcrypt": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-1.0.3.tgz",
"integrity": "sha512-pRyDdo73C8Nim3jwFJ7DWe3TZCgwDfWZ6nHS5LSdU77kWbj1frruvdndP02AOavtD4y8v6Fp2dolbHgp4SDrfg==",
"requires": {
"nan": "2.6.2",
"node-pre-gyp": "0.6.36"
}
},
"bcrypt-pbkdf": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz",
"integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=",
"optional": true,
"requires": {
"tweetnacl": "0.14.5"
}
},
"block-stream": {
"version": "0.0.9",
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
"integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=",
"requires": {
"inherits": "2.0.3"
}
},
"boom": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz",
"integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=",
"requires": {
"hoek": "4.2.1"
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "1.0.0",
"concat-map": "0.0.1"
}
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
},
"combined-stream": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
"requires": {
"delayed-stream": "1.0.0"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"core-js": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz",
"integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4="
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cryptiles": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz",
"integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=",
"requires": {
"boom": "5.2.0"
},
"dependencies": {
"boom": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz",
"integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==",
"requires": {
"hoek": "4.2.1"
}
}
}
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"requires": {
"assert-plus": "1.0.0"
}
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"deep-extend": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz",
"integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8="
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"ecc-jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
"integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=",
"optional": true,
"requires": {
"jsbn": "0.1.1"
}
},
"extend": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
},
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-deep-equal": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz",
"integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
"requires": {
"asynckit": "0.4.0",
"combined-stream": "1.0.6",
"mime-types": "2.1.18"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"fstream": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz",
"integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=",
"requires": {
"graceful-fs": "4.1.11",
"inherits": "2.0.3",
"mkdirp": "0.5.1",
"rimraf": "2.6.2"
}
},
"fstream-ignore": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-1.0.5.tgz",
"integrity": "sha1-nDHa40dnAY/h0kmyTa2mfQktoQU=",
"requires": {
"fstream": "1.0.11",
"inherits": "2.0.3",
"minimatch": "3.0.4"
}
},
"gauge": {
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"requires": {
"aproba": "1.2.0",
"console-control-strings": "1.1.0",
"has-unicode": "2.0.1",
"object-assign": "4.1.1",
"signal-exit": "3.0.2",
"string-width": "1.0.2",
"strip-ansi": "3.0.1",
"wide-align": "1.1.2"
}
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"requires": {
"assert-plus": "1.0.0"
}
},
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"requires": {
"fs.realpath": "1.0.0",
"inflight": "1.0.6",
"inherits": "2.0.3",
"minimatch": "3.0.4",
"once": "1.4.0",
"path-is-absolute": "1.0.1"
}
},
"graceful-fs": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz",
"integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg="
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz",
"integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=",
"requires": {
"ajv": "5.5.2",
"har-schema": "2.0.0"
}
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"hawk": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz",
"integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==",
"requires": {
"boom": "4.3.1",
"cryptiles": "3.1.2",
"hoek": "4.2.1",
"sntp": "2.1.0"
}
},
"hoek": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
"integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA=="
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"requires": {
"assert-plus": "1.0.0",
"jsprim": "1.4.1",
"sshpk": "1.13.1"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "1.4.0",
"wrappy": "1.0.2"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"requires": {
"number-is-nan": "1.0.1"
}
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"jquery": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz",
"integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg=="
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"optional": true
},
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
},
"json-schema-traverse": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.2.3",
"verror": "1.10.0"
}
},
"mime-db": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
"integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
},
"mime-types": {
"version": "2.1.18",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
"integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
"requires": {
"mime-db": "1.33.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "1.1.11"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": {
"minimist": "0.0.8"
}
},
"moment": {
"version": "2.20.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.20.1.tgz",
"integrity": "sha512-Yh9y73JRljxW5QxN08Fner68eFLxM5ynNOAw2LbIB1YAGeQzZT8QFSUvkAz609Zf+IHhhaUxqZK8dG3W/+HEvg=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"nan": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.6.2.tgz",
"integrity": "sha1-5P805slf37WuzAjeZZb0NgWn20U="
},
"node-pre-gyp": {
"version": "0.6.36",
"resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz",
"integrity": "sha1-22BBEst04NR3VU6bUFsXq936t4Y=",
"requires": {
"mkdirp": "0.5.1",
"nopt": "4.0.1",
"npmlog": "4.1.2",
"rc": "1.2.5",
"request": "2.83.0",
"rimraf": "2.6.2",
"semver": "5.5.0",
"tar": "2.2.1",
"tar-pack": "3.4.1"
}
},
"nopt": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
"integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
"requires": {
"abbrev": "1.1.1",
"osenv": "0.1.5"
}
},
"npmlog": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"requires": {
"are-we-there-yet": "1.1.4",
"console-control-strings": "1.1.0",
"gauge": "2.7.4",
"set-blocking": "2.0.0"
}
},
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
"oauth-sign": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1.0.2"
}
},
"os-homedir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
"integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
},
"os-tmpdir": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ="
},
"osenv": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz",
"integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==",
"requires": {
"os-homedir": "1.0.2",
"os-tmpdir": "1.0.2"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"process-nextick-args": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
},
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
},
"qs": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz",
"integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A=="
},
"rc": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.5.tgz",
"integrity": "sha1-J1zWh/bjs2zHVrqibf7oCnkDAf0=",
"requires": {
"deep-extend": "0.4.2",
"ini": "1.3.5",
"minimist": "1.2.0",
"strip-json-comments": "2.0.1"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
}
}
},
"readable-stream": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz",
"integrity": "sha512-vuYxeWYM+fde14+rajzqgeohAI7YoJcHE7kXDAc4Nk0EbuKnJfqtY9YtRkLo/tqkuF7MsBQRhPnPeyjYITp3ZQ==",
"requires": {
"core-util-is": "1.0.2",
"inherits": "2.0.3",
"isarray": "1.0.0",
"process-nextick-args": "2.0.0",
"safe-buffer": "5.1.1",
"string_decoder": "1.0.3",
"util-deprecate": "1.0.2"
}
},
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
},
"request": {
"version": "2.83.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
"integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==",
"requires": {
"aws-sign2": "0.7.0",
"aws4": "1.6.0",
"caseless": "0.12.0",
"combined-stream": "1.0.6",
"extend": "3.0.1",
"forever-agent": "0.6.1",
"form-data": "2.3.2",
"har-validator": "5.0.3",
"hawk": "6.0.2",
"http-signature": "1.2.0",
"is-typedarray": "1.0.0",
"isstream": "0.1.2",
"json-stringify-safe": "5.0.1",
"mime-types": "2.1.18",
"oauth-sign": "0.8.2",
"performance-now": "2.1.0",
"qs": "6.5.1",
"safe-buffer": "5.1.1",
"stringstream": "0.0.5",
"tough-cookie": "2.3.3",
"tunnel-agent": "0.6.0",
"uuid": "3.2.1"
}
},
"rimraf": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"requires": {
"glob": "7.1.2"
}
},
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
},
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"signal-exit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"sntp": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz",
"integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==",
"requires": {
"hoek": "4.2.1"
}
},
"sprintf-js": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.1.tgz",
"integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw="
},
"sshpk": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz",
"integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=",
"requires": {
"asn1": "0.2.3",
"assert-plus": "1.0.0",
"bcrypt-pbkdf": "1.0.1",
"dashdash": "1.14.1",
"ecc-jsbn": "0.1.1",
"getpass": "0.1.7",
"jsbn": "0.1.1",
"tweetnacl": "0.14.5"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"requires": {
"code-point-at": "1.1.0",
"is-fullwidth-code-point": "1.0.0",
"strip-ansi": "3.0.1"
}
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"requires": {
"safe-buffer": "5.1.1"
}
},
"stringstream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "2.1.1"
}
},
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
},
"tar": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz",
"integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=",
"requires": {
"block-stream": "0.0.9",
"fstream": "1.0.11",
"inherits": "2.0.3"
}
},
"tar-pack": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-3.4.1.tgz",
"integrity": "sha512-PPRybI9+jM5tjtCbN2cxmmRU7YmqT3Zv/UDy48tAh2XRkLa9bAORtSWLkVc13+GJF+cdTh1yEnHEk3cpTaL5Kg==",
"requires": {
"debug": "2.6.9",
"fstream": "1.0.11",
"fstream-ignore": "1.0.5",
"once": "1.4.0",
"readable-stream": "2.3.4",
"rimraf": "2.6.2",
"tar": "2.2.1",
"uid-number": "0.0.6"
}
},
"toastr": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/toastr/-/toastr-2.1.4.tgz",
"integrity": "sha1-i0O+ZPudDEFIcURvLbjoyk6V8YE=",
"requires": {
"jquery": "3.3.1"
}
},
"tough-cookie": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.3.tgz",
"integrity": "sha1-C2GKVWW23qkL80JdBNVe3EdadWE=",
"requires": {
"punycode": "1.4.1"
}
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"requires": {
"safe-buffer": "5.1.1"
}
},
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"optional": true
},
"uid-number": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz",
"integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE="
},
"underscore": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
},
"underscore.string": {
"version": "3.3.4",
"resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-3.3.4.tgz",
"integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s=",
"requires": {
"sprintf-js": "1.1.1",
"util-deprecate": "1.0.2"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"uuid": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz",
"integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA=="
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"requires": {
"assert-plus": "1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "1.3.0"
}
},
"wide-align": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
"requires": {
"string-width": "1.0.2"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}
Loading…
Cancel
Save