Comparing version
@@ -23,3 +23,6 @@ { | ||
"tests" | ||
] | ||
], | ||
"dependencies": { | ||
"rltm": "^1.1.0" | ||
} | ||
} |
const gulp = require('gulp'); | ||
const uglify = require("gulp-uglify"); | ||
const browserify = require('browserify'); | ||
const source = require('vinyl-source-stream'); | ||
// task | ||
gulp.task('minify-js', function () { | ||
gulp.src('./src/*.js') | ||
.pipe(uglify()) | ||
.pipe(gulp.dest('./dist')); | ||
gulp.task('compile', function () { | ||
browserify({ | ||
entries: ['window.js'], | ||
debug: true | ||
}) | ||
.bundle() | ||
.pipe(source('ocf.js')) | ||
.pipe(gulp.dest('./web/')); | ||
}); | ||
gulp.task('default', ['minify-js']); | ||
gulp.task('default', ['compile']); | ||
gulp.task('watch', function() { | ||
gulp.watch('./src/*', ['compile']); | ||
}); |
{ | ||
"name": "ocf", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "Open Chat Framework", | ||
"main": "index.js", | ||
"main": "src/index.js", | ||
"scripts": { | ||
@@ -26,5 +26,15 @@ "test": "echo \"Error: no test specified\" && exit 1" | ||
"devDependencies": { | ||
"browserify": "^13.1.0", | ||
"chai": "^3.5.0", | ||
"gulp": "^3.9.1", | ||
"gulp-uglify": "^2.0.0" | ||
"gulp-uglify": "^2.0.0", | ||
"mocha": "^3.1.2", | ||
"vinyl-source-stream": "^1.1.0" | ||
}, | ||
"dependencies": { | ||
"async": "^2.1.2", | ||
"eventemitter2": "^2.2.1", | ||
"rltm": "^1.1.0", | ||
"yuidoc-bootstrap-theme": "^1.0.6" | ||
} | ||
} |
387
README.md
# OCF - Open Chat Framework | ||
With OCF you can build Slack/Flowdock/HipChat and also Skype/Snapchat/WhatsApp. The serverless component runs Socket.IO and optional PubNub microservices. Advanced capabilities with modular Add-ons system. Using PubNub BLOCKS and Socket.IO/Node.JS add-ons for user-mention notifications and data privacy with encryption. | ||
OCF is an object oriented event emitter based framework for building chat applications in Javascript. OCF makes it easy to | ||
build Slack, Flowdock, Discord, Skype, Snapchat, or WhatsApp with ease. | ||
The real time server component is provided by Socket.io or PubNub. OCF is extensible and includes a plugin framework to make dropping in features simple. | ||
# Notes | ||
Open Chat Framework is currently under development. The API is subject to change. | ||
# Examples | ||
## Javascript Examples | ||
* [Chat](https://pubnub.github.io/open-chat-framework/examples/javascript/chat.html) | ||
* [Online List](https://pubnub.github.io/open-chat-framework/examples/javascript/online-list.html) | ||
* [Friends List](https://pubnub.github.io/open-chat-framework/examples/javascript/friends-list.html) | ||
* [Gravatar](https://pubnub.github.io/open-chat-framework/examples/javascript/gravatar.html) | ||
## SDK Integrations | ||
* [jQuery](https://pubnub.github.io/open-chat-framework/examples/jquery/index.html) | ||
* [Angular](https://pubnub.github.io/open-chat-framework/examples/angular/index.html) | ||
* [React](./examples/react/public/index.html) | ||
## Authentication | ||
* [Facebook Login](https://pubnub.github.io/open-chat-framework/examples/3rd-party-login/facebook.html) | ||
## Chatbot | ||
* [NodeJS ChatBot](https://pubnub.github.io/open-chat-framework/examples/bot.js) | ||
## Plugins | ||
Check out the [jQuery](https://pubnub.github.io/open-chat-framework/examples/jquery/index.html) and [Angular](https://pubnub.github.io/open-chat-framework/examples/angular/index.html) examples to see plugins in action. | ||
* [Middleware](./plugins/append.js) | ||
* [Message History](./plugins/messageHistory.js) | ||
* [Search Online Users](./plugins/onlineUserSearch.js) | ||
* [Set a Random Username](./plugins/randomUsername.js) | ||
* [Ian Is Typing... Indicator](./plugins/typingIndicator.js) | ||
## Other usage examples | ||
[Test.js](test.js) includes some usage examples | ||
# Full Docs | ||
You can find the full docs on [the full documentation website](https://pubnub.github.io/open-chat-framework/docs/). | ||
# Table of Contents | ||
<!-- MarkdownTOC --> | ||
- Quick Setup | ||
- Create Instance | ||
- Connect to OCF Network | ||
- Using OCF | ||
- Create a Chat | ||
- Send a message to Chat | ||
- Receive a message from Chat | ||
- Create a bot that automatically replies to a message | ||
- Subscribing to wildcard events | ||
- Classes | ||
- Chats | ||
- Users | ||
- Me | ||
- Plugins | ||
- Event Middleware Plugin | ||
- Registering Plugin | ||
- Server Side Logic | ||
- Development | ||
- Install Rltm | ||
- Install dependencies | ||
- Run Stephen Bot | ||
- Load up example chat page | ||
- Develop / Build for Web | ||
<!-- /MarkdownTOC --> | ||
# Quick Setup | ||
## Create Instance | ||
There are two require fields for initializing OCF. | ||
* ```rltm``` - OCF is based off PubNub [rltm.js](https://github.com/pubnub/rltm.js) which lets you switch between PubNub and Socket.io just by changing your configuration. Check out [the rltm.js docs](https://github.com/pubnub/rltm.js) for more information. | ||
* ```globalChannel``` - This is the global channel that all clients are connected to automatically. It's used for global announcements, global presence, etc. | ||
### Socket.io | ||
```js | ||
let OCF = OpenChatFramework.create({ | ||
rltm: { | ||
service: 'socketio', | ||
config: { | ||
endpoint: 'http://localhost:8000', | ||
} | ||
}, | ||
globalChannel: 'ocf-root-channel' | ||
}); | ||
``` | ||
npm install gulp -g | ||
npm install bower -g | ||
### PubNub | ||
```js | ||
const OCF = OpenChatFramework.create({ | ||
rltm: { | ||
service: 'pubnub', | ||
config: { | ||
publishKey: 'YOUR_PUB_KEY', | ||
subscribeKey: 'YOUR_SUB_KEY' | ||
} | ||
}, | ||
globalChannel: 'ocf-root-channel' | ||
}); | ||
``` | ||
## Connect to OCF Network | ||
Now we're going to connect to the network (defined in ```rltm``` config). In order to connect, we need to identify ourselves to the network. | ||
``` | ||
cd chat-sdk/sdk/ | ||
me = OCF.connect(uuid, {username: username}); | ||
``` | ||
The parameter ```uuid``` is a unique identifier for this client. It can be a user name, user id, email, etc. | ||
The second parameter is a JSON object containing information about this client. This JSON object is sent to all other clients on the network, so no passwords! | ||
This instance of OCF will make all further requests on behalf of the ```uuid``` you supplied. This is commonly called ```me```. | ||
# Using OCF | ||
## Create a Chat | ||
Once OCF is set up, creating and connecting to a chatroom is as simple as: | ||
```js | ||
let chat = new Chat('channel'); | ||
``` | ||
This will automatically connect to the chat room. | ||
## Send a message to Chat | ||
You can send a message to the chat by using the ```send()``` method to broadcast an event over the network. | ||
```js | ||
chat.send('message', 'my message'); | ||
``` | ||
The first parameter is the event to send, and the second parameter is the message payload. | ||
## Receive a message from Chat | ||
You can listen for messages from the chat by subscribing to the ```message``` event using the ```on()``` method. This works over the network, so it'll catch events sent from other clients. That's the whole point right ;) | ||
```js | ||
chat.on('message', (payload) => { | ||
alert('message: ', payload.data.text); | ||
}); | ||
``` | ||
The first parameter is the ```event``` you're listening for. Some event names are reserved (more on that later), but for the most part they can be anything. | ||
The ```payload``` in the event callback is not the raw data you supplied while using ```send```. The payload is augmented with the ```Chat``` that the message was sent from, and the ```User``` that sent it (as defined in ```OCF.connect()```). | ||
```js | ||
{ | ||
chat: Chat, | ||
data: { | ||
text: 'my message' | ||
}, | ||
sender: User | ||
} | ||
``` | ||
## Create a bot that automatically replies to a message | ||
Creating a bot is super easy and we can do it using everything we learned previously. | ||
First, the bot will subscribe to the ```message``` network event. | ||
When a message comes it, it checks the payload to see if the message was sent by itself (to prevent infinite loops). | ||
If it was not sent by itself, it sends a message back to the chatroom repeating the original message that was sent to it. | ||
```js | ||
chat.on('message', (payload) => { | ||
// make sure this is not a message this client sent myself | ||
if(payload.user.data.uuid !== me.data.uuid) { | ||
// send a message back to the chat that sent it | ||
payload.chat.send('message', { | ||
text: 'did you say "' + payload.data.text + '"?'; | ||
}); | ||
} | ||
}); | ||
``` | ||
## Subscribing to wildcard events | ||
You can subscribe to all events a namespace emits by using the ```*``` operator. | ||
```js | ||
chat.on('$ocf.*', (event) => { | ||
}); | ||
``` | ||
You can get any event a chat emits by using the ```onAny``` method. | ||
```js | ||
chat.onAny(() => { | ||
}); | ||
``` | ||
# Classes | ||
## Chats | ||
Chats are objects that emit events. You can subscribe to a chat event with ```chat.on('eventName', () => {})``` or fire a new event with ```chat.emit('eventName', {data})```. | ||
You can get a list of users with ```chat.users```. | ||
## Users | ||
Users represent connected clients. Every user has their own public chatroom called ```feed``` which only that user can publish to as well as a room called ```direct``` which only this user can subscribe to. | ||
## Me | ||
A subclass of ```User```. ```Me``` is returned when you run ```OCF.connect```. Me is the only user that allows the client to set it's own state with ```me.update()```. | ||
# Plugins | ||
Plugins can be registered to do cool things with OCF for free. | ||
An plugin is a typical npm module. Here is an example of a plugin that sets a property on | ||
```Me``` called ```float``` which is equal to some random number. | ||
```js | ||
return { | ||
namespace: '$yourPluginNamespace', | ||
extends: { | ||
Me: { | ||
construct: function() { | ||
// set the parent's username as random integer | ||
this.parent.update({ | ||
float: new Math.random() | ||
}); | ||
}, | ||
getName: function() { | ||
return this.parent.state().float; | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
Every plugin needs to be kept in some ```namespace``` for reasons that will make sense later :). | ||
The property ```extends``` tells OCF what classes this plugin is going to extend. In this example the plugin is extending ```Me```, and you can see that because the key ```Me``` is supplied. | ||
The object supplied as the value for the key ```Me``` configures new methods for the the ```Me``` object. | ||
The method ```construct``` is a unique method that will be run whenever a new ```Me``` is created. The ```construct``` functions and all functions supplied in the plugin run in the context of the object in which they are extending. We can call ```this.parent``` to get access to the instance of ```Me```. | ||
In the example above we use ```this.parent.update()``` to set a value of the user. This state is set | ||
across the network and all other clients will get notified that this client has a new value. | ||
The method ```getName()``` is a generic method that gets added to all instances of ```Me``` under the namespace ```$yourPluginNamespace```. From outside the plugin, you could call ```Me.$yourPluginNamespace.getName()``` to return the value. | ||
## Event Middleware Plugin | ||
It's also possible to register middleware to run before events are sent or received. | ||
The following example registers a function that runs before the event ```message``` is sent over the network. | ||
```js | ||
// middleware tells the framework to use these functions when | ||
// messages are sent | ||
return { | ||
namespace: '$yourPluginNamespace', | ||
middleware: { | ||
send: { | ||
message: function(payload, next) { | ||
// append config.send to the text supplied in the event | ||
payload.data.text += ' appended'; | ||
// continue along middleware | ||
next(null, payload); | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
The property ```middleware``` tells OCF to run these functions during the event cycle. | ||
The property ```send``` tells OCF to run this specific set of functions before the message | ||
is sent over the network. | ||
The property ```message``` tells OCF to run this function only when the ```send``` event is emitted; | ||
To fire this event, you would do: | ||
```js | ||
chat.send('message', {text: 'something'}); | ||
``` | ||
Before the event is broadcast to the chat room, the text ```something``` would be run through | ||
the ```message``` function which would turn it into ```something appended```. | ||
The plugin must call the ```next()``` function with the complete payload when complete. | ||
If there was an error, call ```next('there was an error'); | ||
## Registering Plugin | ||
Plugins must be configured to work with OCF. | ||
```js | ||
OCF.loadPlugin(OpenChatFramework.plugin.typingIndicator({ | ||
timeout: 5000 | ||
})); | ||
``` | ||
The config options supplied here are available as the first parameter in the plugin definition. | ||
# Server Side Logic | ||
If you want to do something like fire a SMS on the server side, you can use PubNub blocks. | ||
You can find an example block in [/examples/javascript/pubnub.block.js](/examples/javascript/pubnub.block.js) which sends an sms message using clicksend every time a message is sent to the [raw javascript chat example](/examples/javascript/chat.html). | ||
# Development | ||
## Install Rltm | ||
Rltm must be installed in sibling directory. | ||
```sh | ||
cd ../ | ||
git clone https://github.com/pubnub/rltm | ||
``` | ||
## Install dependencies | ||
Install gulp and project dependencies. | ||
```sh | ||
npm install | ||
cd ../../realtime.js | ||
bower install | ||
npm install | ||
``` | ||
## Run Stephen Bot | ||
* Requires realtime.js | ||
* End result is minified js file in /dist | ||
* Would be cool if this also worked on back end | ||
```sh | ||
node examples/bot.js | ||
``` | ||
## Load up example chat page | ||
You can use file or localhost. Supply a username in the query string param to make it work properly. | ||
Example: | ||
``` | ||
./open-chat-framework/examples/web.html?username=ian# | ||
``` | ||
# Develop / Build for Web | ||
Compiled using browserify through gulp | ||
```sh | ||
npm install only=dev | ||
npm install gulp -g | ||
Run build task and watch for browser | ||
```sh | ||
gulp && gulp watch | ||
``` |
802
src/index.js
@@ -1,1 +0,801 @@ | ||
var chatSDK = {}; | ||
"use strict"; | ||
// Allows us to create and bind to events. Everything in OCF is an event | ||
// emitter | ||
const EventEmitter2 = require('eventemitter2').EventEmitter2; | ||
// import the rltm.js library from a sister directory | ||
// @todo include this as module | ||
const Rltm = require('rltm'); | ||
// allows a synchronous execution flow. | ||
const waterfall = require('async/waterfall'); | ||
/** | ||
* Global object used to create an instance of OCF. | ||
* | ||
* @class OpenChatFramework | ||
* @constructor | ||
* @param {Object} foo Argument 1 | ||
* @param config.rltm {Object} OCF is based off PubNub [rltm.js](https://github.com/pubnub/rltm.js) which lets you switch between PubNub and Socket.io just by changing your configuration. Check out [the rltm.js docs](https://github.com/pubnub/rltm.js) for more information. | ||
* @param config.globalChannel {String} his is the global channel that all clients are connected to automatically. It's used for global announcements, global presence, etc. | ||
* @return {Object} Returns an instance of OCF | ||
*/ | ||
const create = function(config) { | ||
let OCF = false; | ||
/** | ||
* Configures an event emitter that other OCF objects inherit. Adds shortcut methods for | ||
* ```this.on()```, ```this.emit()```, etc. | ||
* | ||
* @class RootEmitter | ||
* @constructor | ||
*/ | ||
class RootEmitter { | ||
constructor() { | ||
// create an ee2 | ||
this.emitter = new EventEmitter2({ | ||
wildcard: true, | ||
newListener: true, | ||
maxListeners: 50, | ||
verboseMemoryLeak: true | ||
}); | ||
// we bind to make sure wildcards work | ||
// https://github.com/asyncly/EventEmitter2/issues/186 | ||
this.emit = this.emitter.emit.bind(this.emitter); | ||
/** | ||
* Listen for a specific event and fire a callback when it's emitted | ||
* | ||
* @method on | ||
* @param {String} event The event name | ||
* @param {Function} callback The function to run when the event is emitted | ||
*/ | ||
this.on = this.emitter.on.bind(this.emitter); | ||
this.off = this.emitter.off.bind(this.emitter); | ||
/** | ||
* Listen for any event on this object and fire a callback when it's emitted | ||
* | ||
* @method onAny | ||
* @param {Function} callback The function to run when any event is emitted. First parameter is the event name and second is the payload. | ||
*/ | ||
this.onAny = this.emitter.onAny.bind(this.emitter); | ||
/** | ||
* Listen for an event and only fire the callback a single time | ||
* | ||
* @method once | ||
* @param {String} event The event name | ||
* @param {Function} callback The function to run once | ||
*/ | ||
this.once = this.emitter.once.bind(this.emitter); | ||
} | ||
} | ||
/** | ||
* An OCF generic emitter that supports plugins and forwards | ||
* events to a global emitter. | ||
* | ||
* @class Emitter | ||
* @constructor | ||
* @extends RootEmitter | ||
*/ | ||
class Emitter extends RootEmitter { | ||
constructor() { | ||
super(); | ||
// emit an event from this object | ||
this.emit = (event, data) => { | ||
// all events are forwarded to OCF object | ||
// so you can globally bind to events with OCF.on() | ||
OCF.emit(event, data); | ||
// send the event from the object that created it | ||
this.emitter.emit(event, data); | ||
} | ||
// assign the list of plugins for this scope | ||
this.plugins = []; | ||
// bind a plugin to this object | ||
this.plugin = function(module) { | ||
this.plugins.push(module); | ||
// returns the name of the class | ||
let className = this.constructor.name; | ||
// see if there are plugins to attach to this class | ||
if(module.extends && module.extends[className]) { | ||
// attach the plugins to this class | ||
// under their namespace | ||
OCF.addChild(this, module.namespace, | ||
new module.extends[className]); | ||
this[module.namespace].OCF = OCF; | ||
// if the plugin has a special construct function | ||
// run it | ||
if(this[module.namespace].construct) { | ||
this[module.namespace].construct(); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* This is the root {{#crossLink "Chat"}}{{/crossLink}} class that represents a chat room | ||
* | ||
* @class Chat | ||
* @constructor | ||
* @param {String} channel The channel name for the Chat | ||
* @extends Emitter | ||
*/ | ||
class Chat extends Emitter { | ||
constructor(channel) { | ||
super(); | ||
/** | ||
* The channel name for this {{#crossLink "Chat"}}{{/crossLink}} | ||
* | ||
* @property channel | ||
* @type String | ||
*/ | ||
this.channel = channel; | ||
/** | ||
* A list of users in this {{#crossLink "Chat"}}{{/crossLink}}. Automatically kept in sync, | ||
* Use ```Chat.on('$ocf.join')``` and related events to get notified when this changes | ||
* | ||
* @property users | ||
* @type Object | ||
*/ | ||
this.users = {}; | ||
// this.room is our rltm.js connection | ||
this.room = OCF.rltm.join(this.channel); | ||
// whenever we get a message from the network | ||
// run local broadcast message | ||
this.room.on('message', (uuid, data) => { | ||
// all messages are in format [event_name, data] | ||
this.broadcast(data.message[0], data.message[1]); | ||
}); | ||
// forward user join events | ||
this.room.on('join', (uuid, state) => { | ||
let user = this.createUser(uuid, state); | ||
/** | ||
* Broadcast that a {{#crossLink "User"}}{{/crossLink}} has joined the room | ||
* | ||
* @event $ocf.join | ||
* @param {Object} payload.user The {{#crossLink "User"}}{{/crossLink}} that came online | ||
*/ | ||
this.broadcast('$ocf.join', { | ||
user: user | ||
}); | ||
}); | ||
// forward user state change events | ||
this.room.on('state', (uuid, state) => { | ||
this.userUpdate(uuid, state) | ||
}); | ||
// forward user leaving events | ||
this.room.on('leave', (uuid) => { | ||
this.userLeave(uuid); | ||
}); | ||
// forward user leaving events | ||
this.room.on('disconnect', (uuid) => { | ||
this.userDisconnect(uuid); | ||
}); | ||
// get a list of users online now | ||
this.room.here().then((occupants) => { | ||
// for every occupant, create a model user | ||
for(let uuid in occupants) { | ||
// and run the join functions | ||
this.createUser(uuid, occupants[uuid], true); | ||
} | ||
}, (err) => { | ||
throw new Error( | ||
'There was a problem fetching here.', err); | ||
}); | ||
} | ||
/** | ||
* Execute a function when network connection has been made and {{#crossLink "Chat"}}{{/crossLink}} is ready | ||
* | ||
* @method ready | ||
* @param {Function} callback Function to execute when connection is ready | ||
*/ | ||
ready(fn) { | ||
this.room.ready(fn); | ||
} | ||
/** | ||
* Send events to other clients in this {{#crossLink "User"}}{{/crossLink}}. | ||
* Events are broadcast over the network and all events are made | ||
* on behalf of {{#crossLink "Me"}}{{/crossLink}} | ||
* | ||
* @method send | ||
* @param {String} event The event name | ||
* @param {Object} data The event payload object | ||
*/ | ||
send(event, data) { | ||
// create a standardized payload object | ||
let payload = { | ||
data: data, // the data supplied from params | ||
sender: OCF.me.uuid, // my own uuid | ||
chat: this, // an instance of this chat | ||
}; | ||
// run the plugin queue to modify the event | ||
this.runPluginQueue('send', event, (next) => { | ||
next(null, payload); | ||
}, (err, payload) => { | ||
// remove chat otherwise it would be serialized | ||
// instead, it's rebuilt on the other end. | ||
// see this.broadcast | ||
delete payload.chat; | ||
// publish the event and data over the configured channel | ||
this.room.message({ | ||
message: [event, payload], | ||
channel: this.channel | ||
}); | ||
}); | ||
} | ||
/** | ||
* @private | ||
* Broadcasts an event locally to all listeners. | ||
* | ||
* @method broadcast | ||
* @param {String} event The event name | ||
* @param {Object} payload The event payload object | ||
*/ | ||
broadcast(event, payload) { | ||
if(typeof payload == "object") { | ||
// restore chat in payload | ||
if(!payload.chat) { | ||
payload.chat = this; | ||
} | ||
// turn a uuid found in payload.sender to a real user | ||
if(payload.sender && OCF.users[payload.sender]) { | ||
payload.sender = OCF.users[payload.sender]; | ||
} | ||
} | ||
// let plugins modify the event | ||
this.runPluginQueue('broadcast', event, (next) => { | ||
next(null, payload); | ||
}, (err, payload) => { | ||
// emit this event to any listener | ||
this.emit(event, payload); | ||
}); | ||
} | ||
/** | ||
* @private | ||
* Add a user to the {{#crossLink "Chat"}}{{/crossLink}}, creating it if it doesn't already exist. | ||
* | ||
* @method createUser | ||
* @param {String} uuid The user uuid | ||
* @param {Object} state The user initial state | ||
* @param {Boolean} broadcast Force a broadcast that this user is online | ||
*/ | ||
createUser(uuid, state, broadcast = false) { | ||
// Ensure that this user exists in the global list | ||
// so we can reference it from here out | ||
OCF.users[uuid] = OCF.users[uuid] || new User(uuid); | ||
// Add this chatroom to the user's list of chats | ||
OCF.users[uuid].addChat(this, state); | ||
// broadcast the join event over this chatroom | ||
if(!this.users[uuid] || broadcast) { | ||
/** | ||
* Broadcast that a {{#crossLink "User"}}{{/crossLink}} has come online | ||
* | ||
* @event $ocf.online | ||
* @param {Object} payload.user The {{#crossLink "User"}}{{/crossLink}} that came online | ||
*/ | ||
this.broadcast('$ocf.online', { | ||
user: OCF.users[uuid] | ||
}); | ||
} | ||
// store this user in the chatroom | ||
this.users[uuid] = OCF.users[uuid]; | ||
// return the instance of this user | ||
return OCF.users[uuid]; | ||
} | ||
/** | ||
* @private | ||
* Update a user's state within this {{#crossLink "Chat"}}{{/crossLink}}. | ||
* | ||
* @method userUpdate | ||
* @param {String} uuid The {{#crossLink "User"}}{{/crossLink}} uuid | ||
* @param {Object} state State to update for the user | ||
*/ | ||
userUpdate(uuid, state) { | ||
// ensure the user exists within the global space | ||
OCF.users[uuid] = OCF.users[uuid] || new User(uuid); | ||
// if we don't know about this user | ||
if(!this.users[uuid]) { | ||
// do the whole join thing | ||
this.createUser(uuid, state); | ||
} | ||
// update this user's state in this chatroom | ||
this.users[uuid].assign(state, this); | ||
/** | ||
* Broadcast that a {{#crossLink "User"}}{{/crossLink}} has changed state | ||
* | ||
* @event $ocf.state | ||
* @param {Object} payload.user The {{#crossLink "User"}}{{/crossLink}} that changed state | ||
* @param {Object} payload.state The new user state for this ```Chat``` | ||
*/ | ||
this.broadcast('$ocf.state', { | ||
user: this.users[uuid], | ||
state: this.users[uuid].state(this) | ||
}); | ||
} | ||
/** | ||
* Leave from the {{#crossLink "Chat"}}{{/crossLink}} on behalf of {{#crossLink "Me"}}{{/crossLink}} | ||
* | ||
* @method leave | ||
*/ | ||
leave() { | ||
// disconnect from the chat | ||
this.room.leave().then(() => { | ||
// should get caught on as network event | ||
}); | ||
} | ||
/** | ||
* @private | ||
* Perform updates when a user has left the {{#crossLink "Chat"}}{{/crossLink}}. | ||
* | ||
* @method leave | ||
*/ | ||
userLeave(uuid) { | ||
// make sure this event is real, user may have already left | ||
if(this.users[uuid]) { | ||
// if a user leaves, broadcast the event | ||
this.broadcast('$ocf.leave', this.users[uuid]); | ||
this.broadcast('$ocf.offline', this.users[uuid]); | ||
// remove the user from the local list of users | ||
delete this.users[uuid]; | ||
// we don't remove the user from the global list, | ||
// because they may be online in other channels | ||
} else { | ||
// that user isn't in the user list | ||
// we never knew about this user or they already left | ||
// console.log('user already left'); | ||
} | ||
} | ||
/** | ||
* @private | ||
* Fired when a user disconnects from the {{#crossLink "Chat"}}{{/crossLink}} | ||
* | ||
* @method userDisconnect | ||
* @param {String} uuid The uuid of the {{#crossLink "Chat"}}{{/crossLink}} that left | ||
*/ | ||
userDisconnect(uuid) { | ||
// make sure this event is real, user may have already left | ||
if(this.users[uuid]) { | ||
/** | ||
* A {{#crossLink "User"}}{{/crossLink}} has been disconnected from the ```Chat``` | ||
* | ||
* @event $ocf.disconnect | ||
* @param {Object} User The {{#crossLink "User"}}{{/crossLink}} that disconnected | ||
*/ | ||
this.broadcast('$ocf.disconnect', this.users[uuid]); | ||
/** | ||
* A {{#crossLink "User"}}{{/crossLink}} has gone offline | ||
* | ||
* @event $ocf.offline | ||
* @param {Object} User The {{#crossLink "User"}}{{/crossLink}} that has gone offline | ||
*/ | ||
this.broadcast('$ocf.offline', this.users[uuid]); | ||
} | ||
} | ||
/** | ||
* @private | ||
* Load plugins and attach a queue of functions to execute before and | ||
* after events are broadcast or received. | ||
* | ||
* @method runPluginQueue | ||
* @param {String} location Where in the middleeware the event should run (send, broadcast) | ||
* @param {String} event The event name | ||
* @param {String} first The first function to run before the plugins have run | ||
* @param {String} last The last function to run after the plugins have run | ||
*/ | ||
runPluginQueue(location, event, first, last) { | ||
// this assembles a queue of functions to run as middleware | ||
// event is a broadcasted event key | ||
let plugin_queue = []; | ||
// the first function is always required | ||
plugin_queue.push(first); | ||
// look through the configured plugins | ||
for(let i in this.plugins) { | ||
// if they have defined a function to run specifically | ||
// for this event | ||
if(this.plugins[i].middleware | ||
&& this.plugins[i].middleware[location] | ||
&& this.plugins[i].middleware[location][event]) { | ||
// add the function to the queue | ||
plugin_queue.push( | ||
this.plugins[i].middleware[location][event]); | ||
} | ||
} | ||
// waterfall runs the functions in assigned order | ||
// waiting for one to complete before moving to the next | ||
// when it's done, the ```last``` parameter is called | ||
waterfall(plugin_queue, last); | ||
} | ||
/** | ||
* @private | ||
* Set the state for {{#crossLink "Me"}}{{/crossLink}} within this {{#crossLink "User"}}{{/crossLink}}. | ||
* Broadcasts the ```$ocf.state``` event on other clients | ||
* | ||
* @method setState | ||
* @param {Object} state The new state {{#crossLink "Me"}}{{/crossLink}} will have within this {{#crossLink "User"}}{{/crossLink}} | ||
*/ | ||
setState(state) { | ||
// handy method to set state of user without touching rltm | ||
this.room.state(state); | ||
} | ||
}; | ||
/** | ||
* This is our User class which represents a connected client | ||
* | ||
* @class User | ||
* @constructor | ||
* @extends Emitter | ||
*/ | ||
class User extends Emitter { | ||
constructor(uuid, state = {}, chat = OCF.globalChat) { | ||
super(); | ||
/** | ||
* the User's uuid. This is public id exposed to the network. | ||
* | ||
* @property uuid | ||
* @type String | ||
*/ | ||
this.uuid = uuid; | ||
/** | ||
* keeps account of user state in each channel | ||
* | ||
* @property states | ||
* @type Object | ||
*/ | ||
this.states = {}; | ||
/** | ||
* keep a list of chatrooms this user is in | ||
* | ||
* @property chats | ||
* @type Object | ||
*/ | ||
this.chats = {}; | ||
/** | ||
* every user has a couple personal rooms we can connect to | ||
* feed is a list of things a specific user does that | ||
* many people can subscribe to | ||
* | ||
* @property feed | ||
* @type Chat | ||
*/ | ||
this.feed = new Chat( | ||
[OCF.globalChat.channel, 'feed', uuid].join('.')); | ||
/** | ||
* direct is a private channel that anybody can publish to | ||
* but only the user can subscribe to | ||
* this permission based system is not implemented yet | ||
* | ||
* @property direct | ||
* @type Chat | ||
*/ | ||
this.direct = new Chat( | ||
[OCF.globalChat.channel, 'direct', uuid].join('.')); | ||
// if the user does not exist at all and we get enough | ||
// information to build the user | ||
if(!OCF.users[uuid]) { | ||
OCF.users[uuid] = this; | ||
} | ||
// update this user's state in it's created context | ||
this.assign(state, chat) | ||
} | ||
/** | ||
* get the user's state in a chatroom | ||
* | ||
* @method state | ||
* @param {Chat} chat Chatroom to retrieve state from | ||
*/ | ||
state(chat = OCF.globalChat) { | ||
return this.states[chat.channel] || {}; | ||
} | ||
/** | ||
* update the user's state in a specific chatroom | ||
* | ||
* @method update | ||
* @param {Object} state The new state for the user | ||
* @param {Chat} chat Chatroom to retrieve state from | ||
*/ | ||
update(state, chat = OCF.globalChat) { | ||
let chatState = this.state(chat) || {}; | ||
this.states[chat.channel] = Object.assign(chatState, state); | ||
} | ||
/** | ||
* @private | ||
* this is only called from network updates | ||
* | ||
* @method assign | ||
*/ | ||
assign(state, chat) { | ||
this.update(state, chat); | ||
} | ||
/** | ||
* @private | ||
* adds a chat to this user | ||
* | ||
* @method addChat | ||
*/ | ||
addChat(chat, state) { | ||
// store the chat in this user object | ||
this.chats[chat.channel] = chat; | ||
// updates the user's state in that chatroom | ||
this.assign(state, chat); | ||
} | ||
} | ||
/** | ||
* Represents the client connection as a {{#crossLink "User"}}{{/crossLink}}. | ||
* Has the ability to update it's state on the network. An instance of | ||
* {{#crossLink "Me"}}{{/crossLink}} is returned by the ```OCF.connect()``` | ||
* method. | ||
* | ||
* @class Me | ||
* @constructor | ||
* @param {String} uuid The uuid of this user | ||
* @extends User | ||
*/ | ||
class Me extends User { | ||
constructor(uuid) { | ||
// call the User constructor | ||
super(uuid); | ||
} | ||
// assign updates from network | ||
assign(state, chat) { | ||
// we call "update" because calling "super.assign" | ||
// will direct back to "this.update" which creates | ||
// a loop of network updates | ||
super.update(state, chat); | ||
} | ||
/** | ||
* Update this user state over the network | ||
* | ||
* @method update | ||
* @param {Object} state The new state for {{#crossLink "Me"}}{{/crossLink}} | ||
* @param {Chat} chat An instance of the {{#crossLink "Chat"}}{{/crossLink}} where state will be updated. | ||
* Defaults to ```OCF.globalChat```. | ||
*/ | ||
update(state, chat = OCF.globalChat) { | ||
// run the root update function | ||
super.update(state, chat); | ||
// publish the update over the global channel | ||
chat.setState(state); | ||
} | ||
} | ||
/** | ||
* Provides the base Widget class... | ||
* | ||
* @class OCF | ||
*/ | ||
const init = function() { | ||
// Create the root OCF object | ||
OCF = new RootEmitter; | ||
// stores config vars | ||
OCF.config = config || {}; | ||
// set a default global channel if none is set | ||
OCF.config.globalChannel = OCF.config.globalChannel || 'ocf-global'; | ||
// create a global list of known users | ||
OCF.users = {}; | ||
// define our global chatroom all users join by default | ||
OCF.globalChat = false; | ||
// define the user that this client represents | ||
OCF.me = false; | ||
// store a reference to the rltm.js networking library | ||
OCF.rltm = false; | ||
/** | ||
* connect to realtime service and create instance of {{#crossLink "Me"}}{{/crossLink}} | ||
* | ||
* @method connect | ||
* @param {String} uuid The uuid for {{#crossLink "Me"}}{{/crossLink}} | ||
* @param {Object} state The initial state for {{#crossLink "Me"}}{{/crossLink}} | ||
* @return {Me} me an instance of me | ||
*/ | ||
OCF.connect = function(uuid, state) { | ||
// make sure the uuid is set for this client | ||
if(!uuid) { | ||
throw new Error('You must supply a uuid as the ' + | ||
'first parameter when connecting.'); | ||
} | ||
// this creates a user known as Me and | ||
// connects to the global chatroom | ||
this.config.rltm.config.uuid = uuid; | ||
this.config.rltm.config.state = state; | ||
// configure the rltm plugin with the params set in config method | ||
this.rltm = new Rltm(config.rltm); | ||
// create a new chat to use as globalChat | ||
this.globalChat = new Chat(config.globalChannel); | ||
// create a new user that represents this client | ||
this.me = new Me(uuid); | ||
// create a new instance of Me using input parameters | ||
this.globalChat.createUser(uuid, state); | ||
this.me.update(state); | ||
// return me | ||
return this.me; | ||
// client can access globalChat through OCF.globalChat | ||
}; | ||
// our exported classes | ||
OCF.Chat = Chat; | ||
OCF.User = User; | ||
// add an object as a subobject under a namespoace | ||
OCF.addChild = (ob, childName, childOb) => { | ||
// assign the new child object as a property of parent under the | ||
// given namespace | ||
ob[childName] = childOb; | ||
// the new object can use ```this.parent``` to access | ||
// the root class | ||
childOb.parent = ob; | ||
} | ||
return OCF; | ||
} | ||
// return an instance of OCF | ||
return init(); | ||
} | ||
// export the OCF api | ||
module.exports = { | ||
plugin: {}, // leave a spot for plugins to exist | ||
create: create | ||
}; |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 5 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Minified code
QualityThis package contains minified code. This may be harmless in some cases where minified code is included in packaged libraries, however packages on npm should not minify code.
Found 1 instance in 1 package
Mixed license
License(Experimental) Package contains multiple licenses.
Found 1 instance in 1 package
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 1 instance in 1 package
6798099
3669.83%253
1586.67%80692
1580.03%390
1595.65%4
Infinity%6
200%2
Infinity%80
-20%3
50%12
Infinity%5
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added