Reference Source

js/services/uused_base/unused_socketio_service_provider.js

// NPM IMPORTS
import assert from 'assert'
import Baconjs from 'baconjs'

// COMMON IMPORTS
import T           from 'devapt-core-common/dist/js/utils/types'
import Credentials from 'devapt-core-common/dist/js/base/credentials'
import ServiceProvider from 'devapt-core-common/dist/js/services/service_provider'

// SERVER IMPORTS
import runtime from '../../base/runtime'


let context = 'server/services/base/socketio_service_provider'



/**
 * Service provider base class with SocketIO provider features.
 * @abstract
 * @author Luc BORIES
 * @license Apache-2.0
 */
export default class SocketIOServiceProvider extends ServiceProvider
{
	/**
	 * Create a service provider.
	 * 
	 * @param {string} arg_provider_name - consumer name.
	 * @param {Service} arg_service_instance - service instance.
	 * @param {string} arg_context - logging context label.
	 * 
	 * @returns {nothing}
	 */
	constructor(arg_provider_name, arg_service_instance, arg_context)
	{
		assert( T.isString(arg_provider_name), context + ':bad provider name string')
		assert( T.isObject(arg_service_instance) && arg_service_instance.is_service, context + ':bad service object')
		
		super(arg_provider_name, arg_service_instance, arg_context ? arg_context : context)
		
		this.is_socketio_service_provider = true
		
		this.subscribers_sockets = []
		this.runtime = runtime
		
		// TRACE
		// this.enable_trace()
		
		// ACTIVATE SERVICE ON SOCKETIO SERVER FOR BROWSER REQUEST
		this.activate_on_socketio_servers()
		
		
		// CREATE AN OUTPUT STREAM AND A TRANSFORMED OUTPUT STREAM
		const self = this
		self.provided_values_stream = new Baconjs.Bus()
		if ( T.isFunction(this.init_provided_values_stream) )
		{
			this.init_provided_values_stream()
		}
		const post_cb = (v) => {
			// console.log(context + ':on stream value for provider %s',  arg_provider_name)
			self.post_provided_values_to_subscribers(v)
		}
		self.provided_values_stream.onValue(post_cb)
	}
	
	
	
	/**
	 * Post a message on the bus.
	 * 
	 * @param {object} arg_msg - message payload.
	 * 
	 * @returns {nothing}
	 */
	post_provided_values_to_subscribers(arg_datas)
	{
		const svc_name = this.service.get_name()
		// console.log(context + ':post:emit datas for ' + svc_name + ' with subscribers:' + this.subscribers_sockets.length)
		this.subscribers_sockets.forEach(
			(socket) => {
				// console.log(context + ':post:emit datas for ' + svc_name)
				socket.emit('post', { service:svc_name, operation:'post', result:'done', datas:arg_datas })
			}
		)
		
	}
	
	
	/**
	 * Get service provider operations.
	 * 
	 * @param {object} arg_socket - client socket.
	 * 
	 * @returns {object} - plain object of operations as name:callback
	 */
	get_io_operations(arg_socket)
	{
		const self = this
		const svc_name = self.service.get_name()
		
		return {
			// SETTINGS
			'request_settings':
				(data, credentials) => {
					const defined_topology = runtime.get_defined_topology()
					const defined_svc = defined_topology.find_service_with_credentials(credentials, svc_name, 'services')
					const svc_settings_js = defined_svc ? defined_svc.export_settings() : {}
					const svc_browser_settings_js = svc_settings_js.browser ? svc_settings_js.browser : {}
					arg_socket.emit('reply_settings', { svc: svc_name, settings:svc_browser_settings_js })
				},
			
			// SOCKET OPERATIONS
			'disconnect':
				() => {
					self.info('activate_on_socketio_server:socket disconnected from /' + svc_name)
					arg_socket.emit('bye bye from /' + svc_name, { from: 'world' })
				},
			'end':
				() => {
					arg_socket.disconnect(0)
				},
			'ping':
				() => {
					// console.info(context + ':activate_on_socketio_server:socket get on /' + svc_name, socket.id, data)
					arg_socket.emit('pong', '/' + svc_name + '/ping response')
				},
			
			// SECURITY OPERATIONS
			'login':
				(data, arg_credentials) => {
					self.on_method('login', arg_socket, data, arg_credentials)
				},
			'signup':
				(data, arg_credentials) => {
					self.on_method('signup', arg_socket, data, arg_credentials)
				},
			'logout':
				(data, arg_credentials) => {
					self.on_method('logout', arg_socket, data, arg_credentials)
				},
			'renew':
				(data, arg_credentials) => {
					self.on_method('renew', arg_socket, data, arg_credentials)
				},
			'change_password':
				(data, arg_credentials) => {
					self.on_method('change_password', arg_socket, data, arg_credentials)
				},
			'reset_password':
				(data, arg_credentials) => {
					self.on_method('reset_password', arg_socket, data, arg_credentials)
				},
				
			// SUBSCRIPTION OPERATIONS
			'subscribe':
				(data, arg_credentials) => {
					self.subscribe(arg_socket, data, arg_credentials)
				},
			'unsubscribe':
				(data, arg_credentials) => {
					self.unsubscribe(arg_socket, data, arg_credentials)
				},
			
			// OTHERS OPERATIONS
			'get':
				(data, arg_credentials) => {
					self.on_method('get', arg_socket, data, arg_credentials)
				},
			'render':
				(data, arg_credentials) => {
					self.on_method('render', arg_socket, data, arg_credentials)
				},
			'list':
				(data, arg_credentials) => {
					self.on_method('list', arg_socket, data, arg_credentials)
				},
			'push':
				(data, arg_credentials) => {
					self.on_method('push', arg_socket, data, arg_credentials)
				}
		}
	}
	
	
	/**
	 * Activate service on socketio server for browser request with messages.
	 * @returns {nothing}
	 */
	activate_on_socketio_servers()
	{
		const self = this
		const svc_name = self.service.get_name()
		
		self.debug('activate_on_socketio_servers:svc=' + svc_name)
		
		Object.keys(runtime.socketio_servers).forEach(
			(server_name) => {
				const socketio_server = runtime.socketio_servers[server_name]
				
				self.debug('activate_on_socketio_servers:svc=' + svc_name + ':server=' + server_name)
				
				self.activate_on_socketio_server(socketio_server)
			}
		)
		
		if ( T.isFunction(self.activate_on_socketio_servers_self) )
		{
			self.activate_on_socketio_servers_self()
		}
	}
	
	
	/**
	 * Activate service on one socketio server for browser request with messages.
	 * 
	 * @param {object} arg_socketio - socketio server.
	 * 
	 * @returns {nothing}
	 */
	activate_on_socketio_server(arg_socketio)
	{
		const self = this
		const svc_name = self.service.get_name()
		const serverio_svc = arg_socketio.of(svc_name)
		
		// console.log(context + ':activate_on_socketio_server:svc=' + svc_name + ':socket.id=' + serverio_svc.id)
		
		serverio_svc.on('connection',
			(socket) => {
				self.info('activate_on_socketio_server:new connection on /' + svc_name, socket.id)
				
				const ops = self.get_io_operations(socket)
				Object.keys(ops).forEach(
					(key) => {
						const callback = ops[key]
						const check_cb = (data) => {
							// LOGIN SPECIAL CASE: NO CREDENTIALS YET!
							const no_credentials_ops = ['login', 'disconnect', 'end', 'ping']
							if ( no_credentials_ops.indexOf(key) > -1 )
							{
								// console.info(context + ':activate_on_socketio_server:operation %s of svc %s without credentials with data:', key, svc_name, data)
								callback(data, undefined)
								return
							}
							
							// console.info(context + ':activate_on_socketio_server:operation %s indexOf:', key, no_credentials_ops.indexOf(key))
							
							// CHECK CREDENTIALS
							if ( ! data || ! data.credentials )
							{
								socket.emit(key, { service:svc_name, operation:key, result:'authorization failure' })
								self.error('bad credentials')
								console.error(context + ':activate_on_socketio_server:bad credentials for method %s of svc %s with data:', key, svc_name, data)
								return
							}
							
							// console.log(context + ':on: svc=%s op=%s :arg_credentials', svc_name, key, data.credentials.username)
							const credentials = new Credentials(data.credentials)
							runtime.security().authenticate(credentials)
							.then(
								(authenticate_result) => {
									if (authenticate_result)
									{
										self.debug('authentication success')
										// console.log(context + 'authentication success')
										
										const permission = { resource:svc_name, operation:key }
										let authorization_promise = runtime.security().authorize(permission, credentials)
										return authorization_promise.then(
											(authorize_result) => {
												// console.log(context + ':authorize_result', authorize_result)
												if (authorize_result)
												{
													self.debug('authorization success')
													// console.log(context + ':authorization success')
													callback(data, credentials)
													return true
												}
												
												self.debug('authorization failure')
												console.log(context + ':authorization failure')
												socket.emit(key, { service:svc_name, operation:key, result:'authorization failure' })
												return false
											}
										)
										.catch(
											(reason) => {
												self.debug('authorization error:' + reason)
												console.error(context + ':authorization error:' + reason)
												socket.emit(key, { service:svc_name, operation:key, result:'authorization error'} )
											}
										)
									}
									
									self.debug('authentication failure')
									console.log(context + 'authentication failure')
									socket.emit(key, { service:svc_name, operation:key, result:'authentication failure' })
									return
								}
							)
							.catch(
								(reason) => {
									self.debug('authentication error:' + reason) // TODO NOT ONLY AUTHORIZ. ERROR
									console.log(context + 'authentication error:' + reason)
									socket.emit(key, { service:svc_name, operation:key, result:'authentication error'} )
								}
							)
						}
						
						socket.on(key, check_cb)
					}
				)
				
				if ( T.isFunction(self.activate_on_socketio_server_self) )
				{
					self.activate_on_socketio_server_self(arg_socketio, socket)
				}
			}
		)
	}
	
	
	
	/**
	 * Add a subscriber socket.
	 * 
	 * @param {object} arg_socket - subscribing socket.
	 * @param {object} arg_data - subscribing filter or datas (optional).
	 * 
	 * @returns {nothing}
	 */
	subscribe(arg_socket/*, arg_data*/)
	{
		const svc_name = this.service.get_name()
		// console.info(context + ':subscribe:socket subscribe on /' + svc_name, arg_socket.id)
		
		this.subscribers_sockets.push(arg_socket)
		arg_socket.emit('subscribe', { service:svc_name, operation:'subscribe', result:'done' })
	}
	
	
	
	/**
	 * Remove a subscriber socket.
	 * 
	 * @param {object} arg_socket - subscribing socket.
	 * @param {object} arg_data - subscribing filter or datas (optional).
	 * 
	 * @returns {nothing}
	 */
	unsubscribe(arg_socket, arg_data)
	{
		const svc_name = this.service.get_name()
		self.error(context + ':unsubscribe:not yet implemented on /' + svc_name, arg_socket.id, arg_data)
		
		arg_socket.emit('unsubscribe', { service:svc_name, operation:'unsubscribe', result:'not implemented' })
	}
	
	
	
	/**
	 * Get operation handler on socket.
	 * 
	 * @param {string} arg_method - method name
	 * @param {object} arg_socket - subscribing socket.
	 * @param {object} arg_data - query filter or datas (optional).
	 * @param {Credentials} arg_credentials - request credentials
	 * 
	 * @returns {nothing}
	 */
	on_method(arg_method, arg_socket, arg_data, arg_credentials)
	{
		const svc_name = this.service.get_name()
		// console.info(context + ':on_get:socket get on /' + svc_name, arg_socket.id, arg_data)
		
		// CHECK REQUEST
		if ( ! T.isObject(arg_data) || ! T.isObject(arg_data.request) || ! T.isArray(arg_data.request.operands))
		{
			return Promise.reject('bad data request')
		}
		
		// PROCESS REQUEST
		const datas_promise = this.process(arg_method, arg_data.request.operands, arg_credentials)
		datas_promise.then(
			(produced_datas) => {
				arg_socket.emit(arg_method, { 'service':svc_name, 'operation':arg_method, 'result':'done', 'datas':produced_datas })
			},
			
			(reason) => {
				arg_socket.emit(arg_method, { 'service':svc_name, 'operation':arg_method, 'result':'error', 'datas':reason })
			}
		)
	}
	
	
	
	/**
	 * Process request and returns datas.
	 * @abstract
	 * 
	 * @param {string} arg_method - method name
	 * @param {array} arg_operands - request operands
	 * @param {Credentials} arg_credentials - request credentials
	 * 
	 * @returns {Promise}
	 */
	process(/*arg_method, arg_operands, arg_credentials*/)
	{
		return Promise.reject('not yet implemented')
	}
}