import stampit from 'stampit';
import {Meta, Model} from './base';
import Request from '../request';
import {EventEmittable} from '../utils';
import _ from 'lodash';
import QuerySet from '../querySet';
const ChannelQuerySet = stampit().compose(QuerySet).methods({
/**
* Publishes to a channel.
* @memberOf QuerySet
* @instance
* @param {Object} channel
* @param {Object} message
* @param {String} [room = null]
* @returns {QuerySet}
* @example {@lang javascript}
* Channel.please().publish({ instanceName: 'test-instace', name: 'test-class' }, { content: 'my message'});
*/
publish(properties, message, room = null) {
this.properties = _.assign({}, this.properties, properties);
this.payload = {payload: JSON.stringify(message)};
if (room) {
this.payload.room = room;
}
this.method = 'POST';
this.endpoint = 'publish';
return this;
},
/**
* Allows polling of a channel.
* @memberOf QuerySet
* @instance
* @param {Object} options
* @param {Boolean} [start = true]
* @returns {ChannelPoll}
* @example {@lang javascript}
* var poll = Channel.please().poll({ instanceName: 'test-instace', name: 'test-class' });
*
* poll.on('start', function() {
* console.log('poll::start');
* });
*
* poll.on('stop', function() {
* console.log('poll::stop');
* });
*
* poll.on('message', function(message) {
* console.log('poll::message', message);
* });
*
* poll.on('custom', function(message) {
* console.log('poll::custom', message);
* });
*
* poll.on('create', function(data) {
* console.log('poll::create', data);
* });
*
* poll.on('delete', function(data) {
* console.log('poll::delete', data);
* });
*
* poll.on('update', function(data) {
* console.log('poll::update', data);
* });
*
* poll.on('error', function(error) {
* console.log('poll::error', error);
* });
*
* poll.start();
*
*/
poll(properties = {}, options = {}, start = true) {
this.properties = _.assign({}, this.properties, properties);
const config = this.getConfig();
const meta = this.model.getMeta();
const path = meta.resolveEndpointPath('poll', this.properties);
options.path = path;
const channelPoll = ChannelPoll.setConfig(config)(options);
if (start === true) {
channelPoll.start();
}
return channelPoll;
},
history(properties = {}, query = {}) {
this.properties = _.assign({}, this.properties, properties);
this.method = 'GET';
this.endpoint = 'history';
this.query = query;
this._serialize = false;
return this;
}
});
const ChannelMeta = Meta({
name: 'channel',
pluralName: 'channels',
endpoints: {
'detail': {
'methods': ['delete', 'patch', 'put', 'get'],
'path': '/v1.1/instances/{instanceName}/channels/{name}/'
},
'list': {
'methods': ['post', 'get'],
'path': '/v1.1/instances/{instanceName}/channels/'
},
'poll': {
'methods': ['get'],
'path': '/v1.1/instances/{instanceName}/channels/{name}/poll/'
},
'publish': {
'methods': ['post'],
'path': '/v1.1/instances/{instanceName}/channels/{name}/publish/'
},
'history': {
'methods': ['get'],
'path': '/v1.1/instances/{instanceName}/channels/{name}/history/'
}
}
});
const channelConstraints = {
instanceName: {
presence: true,
length: {
minimum: 5
}
},
name: {
presence: true,
string: true,
length: {
minimum: 5
}
},
description: {
string: true
},
type: {
inclusion: ['default', 'separate_rooms']
},
group: {
numericality: true
},
group_permissions: {
inclusion: ['none', 'subscribe', 'publish']
},
other_permissions: {
inclusion: ['none', 'subscribe', 'publish']
},
custom_publish: {
boolean: true
}
};
/**
* Wrapper around {@link http://docs.syncano.io/v0.1/docs/channels-poll|channels poll} endpoint which implements `EventEmitter` interface.
* Use it via `Channel` poll method.
* @constructor
* @type {ChannelPoll}
* @property {Number} [timeout = 300000] 5 mins
* @property {String} [path = null] request path
* @property {Number} [lastId = null] used internally in for loop
* @property {Number} [room = null]
* @property {Boolean} [abort = false] used internally to conrole for loop
* @example {@lang javascript}
* var poll = ChannelPoll.setConfig(config)({
* path: '/v1.1/instances/some-instance/channels/some-channel/poll/'
* });
*
* poll.on('start', function() {
* console.log('poll::start');
* });
*
* poll.on('stop', function() {
* console.log('poll::stop');
* });
*
* poll.on('message', function(message) {
* console.log('poll::message', message);
* });
*
* poll.on('custom', function(message) {
* console.log('poll::custom', message);
* });
*
* poll.on('create', function(data) {
* console.log('poll::create', data);
* });
*
* poll.on('delete', function(data) {
* console.log('poll::delete', data);
* });
*
* poll.on('update', function(data) {
* console.log('poll::update', data);
* });
*
* poll.on('error', function(error) {
* console.log('poll::error', error);
* });
*
* poll.start();
*
*/
export const ChannelPoll = stampit()
.compose(Request, EventEmittable)
.props({
timeout: 1000 * 60 * 5,
path: null,
lastId: null,
room: null,
abort: false
})
.methods({
request() {
const options = {
timeout: this.timeout,
query: {
last_id: this.lastId,
room: this.room
}
};
this.emit('request', options);
return this.makeRequest('GET', this.path, options);
},
start() {
this.emit('start');
// some kind of while loop which uses Promises
const loop = () => {
if (this.abort === true) {
this.emit('stop');
return
}
return this.request()
.then((message) => {
this.emit('message', message);
this.emit(message.action, message);
this.lastId = message.id;
return message;
})
.finally(loop)
.catch((error) => {
if (error.timeout && error.timeout === this.timeout) {
return this.emit('timeout', error);
}
this.emit('error', error);
this.stop();
});
}
process.nextTick(loop);
return this.stop;
},
stop(removeListeners = false) {
this.abort = true;
if(removeListeners) {
this.removeAllListeners();
}
return this;
}
});
/**
* OO wrapper around channels {@link http://docs.syncano.io/v0.1/docs/channels-list endpoint}.
* **Channel** has two special methods called ``publish`` and ``poll``. First one will send message to the channel and second one will create {@link http://en.wikipedia.org/wiki/Push_technology#Long_polling long polling} connection which will listen for messages.
* @constructor
* @type {Channel}
* @property {String} name
* @property {String} instanceName
* @property {String} type
* @property {Number} [group = null]
* @property {String} [group_permissions = null]
* @property {String} [other_permissions = null]
* @property {Boolean} [custom_publish = null]
* @example {@lang javascript}
* Channel.please().get('instance-name', 'channel-name').then((channel) => {
* return channel.publish({x: 1});
* });
*
* Channel.please().get('instance-name', 'channel-name').then((channel) => {
* const poll = channel.poll();
*
* poll.on('start', function() {
* console.log('poll::start');
* });
*
* poll.on('stop', function() {
* console.log('poll::stop');
* });
*
* poll.on('message', function(message) {
* console.log('poll::message', message);
* });
*
* poll.on('custom', function(message) {
* console.log('poll::custom', message);
* });
*
* poll.on('create', function(data) {
* console.log('poll::create', data);
* });
*
* poll.on('delete', function(data) {
* console.log('poll::delete', data);
* });
*
* poll.on('update', function(data) {
* console.log('poll::update', data);
* });
*
* poll.on('error', function(error) {
* console.log('poll::error', error);
* });
*
* poll.start();
* });
*/
const Channel = stampit()
.compose(Model)
.setMeta(ChannelMeta)
.setQuerySet(ChannelQuerySet)
.methods({
poll(options = {}, start = true) {
const config = this.getConfig();
const meta = this.getMeta();
const path = meta.resolveEndpointPath('poll', this);
options.path = path;
const channelPoll = ChannelPoll.setConfig(config)(options);
if (start === true) {
channelPoll.start();
}
return channelPoll;
},
publish(message, room = null) {
const options = {
payload: {
payload: JSON.stringify(message)
}
};
const meta = this.getMeta();
const path = meta.resolveEndpointPath('publish', this);
if (room !== null) {
options.payload.room = room;
}
return this.makeRequest('POST', path, options);
},
history(query = {}) {
const meta = this.getMeta();
const path = meta.resolveEndpointPath('history', this);
return this.makeRequest('GET', path, {query});
}
})
.setConstraints(channelConstraints);
export default Channel;