<commit_before>( function( mw, $ ) {"use strict";

	mw.PluginManager.add( 'liveCore', mw.KBasePlugin.extend({

		firstPlay : false,
		/**
		 * API requests interval for updating live stream status (Seconds).
		 * Default is 30 seconds, to match server's cache expiration
		 */
		liveStreamStatusInterval : 30,

		// Default DVR Window (Seconds)
		defaultDVRWindow : 30 * 60,

		onAirStatus: true,

		dvrTimePassed: 0,

		defaultConfig: {
			//whether to start backwards timer on pause in iOS
			updateIOSPauseTime: false,
			//time in ms to wait before displaying the offline alert
			offlineAlertOffest: 1000,
			//disable the islive check (force live to true)
			disableLiveCheck: false
		},

		/**
		 * indicates the last "current time" displayed. (will be used in iOS - where we sometimes override the current time)
		 */
		lastShownTime: 0,

		/**
		 * (only for iOS) indicates we passed the dvr window size and once we will seek backwards we should reAttach timUpdate events
		 */
		shouldReAttachTimeUpdate: false,

		setup: function() {
			this.addPlayerBindings();
			this.extendApi();
		},
		/**
		 * Extend JS API to match the KDP
		 */
		extendApi: function() {
			var _this = this;

			this.getPlayer().isOffline = function() {
				return !_this.onAirStatus;
			}
		},

		addPlayerBindings: function() {
			var _this = this;
			var embedPlayer = this.getPlayer();

			this.bind( 'playerReady', function() {
				//ui components to hide
				var showComponentsArr = [];
				//ui components to show
				var hideComponentsArr = [];
				hideComponentsArr.push( 'liveBackBtn' );
				_this.dvrTimePassed = 0;
				_this.lastShownTime = 0;

				//live entry
				if ( embedPlayer.isLive() ) {
					_this.addLiveStreamStatusMonitor();
					//hide source selector until we support live streams switching
					hideComponentsArr.push( 'sourceSelector' );
					embedPlayer.disablePlayControls();
					embedPlayer.addPlayerSpinner();
					_this.getLiveStreamStatusFromAPI( function( onAirStatus ) {
						embedPlayer.hideSpinner();
					} );
					_this.switchDone = true;
					if ( embedPlayer.sequenceProxy ) {
						_this.switchDone = false;
					}

					//live + DVR
					if ( _this.isDVR() ) {
						_this.dvrWindow = embedPlayer.evaluate( '{mediaProxy.entry.dvrWindow}' ) * 60;
						if ( !_this.dvrWindow ) {
							_this.dvrWindow = this.defaultDVRWindow;
						}
						if ( kWidget.isIOS() ) {
							embedPlayer.setDuration( _this.dvrWindow );
						}
						showComponentsArr.push( 'scrubber', 'durationLabel', 'currentTimeLabel' );
					} else {  //live + no DVR
						showComponentsArr.push( 'liveStatus' );
						hideComponentsArr.push( 'scrubber', 'durationLabel', 'currentTimeLabel' );
					}
				}
				//not a live etnry: restore ui, hide live ui
				else {
					hideComponentsArr.push( 'liveStatus' );
					showComponentsArr.push( 'sourceSelector', 'scrubber', 'durationLabel', 'currentTimeLabel' );
					_this.removeLiveStreamStatusMonitor();
				}

				embedPlayer.triggerHelper('onShowInterfaceComponents', [ showComponentsArr ] );
				embedPlayer.triggerHelper('onHideInterfaceComponents', [ hideComponentsArr ] );
			} );

			this.bind( 'onpause', function() {
				if ( _this.isDVR() && _this.switchDone ) {
					embedPlayer.addPlayerSpinner();
					_this.getLiveStreamStatusFromAPI( function( onAirStatus ) {
						if ( onAirStatus ) {
							if ( _this.shouldHandlePausedMonitor() ) {
								_this.addPausedMonitor();
							}
						}
					} );
				}
			} );

			this.bind( 'firstPlay', function() {
				_this.firstPlay = true;
			} );

			this.bind( 'AdSupport_PreSequenceComplete', function() {
				_this.switchDone = true;
			} );

			this.bind( 'liveStreamStatusUpdate', function( e, onAirObj ) {
				//if we moved from live to offline  - show message
				if ( _this.onAirStatus && !onAirObj.onAirStatus ) {
					//simetimes offline is only for a second and the message is not needed..
					setTimeout( function() {
						if ( !_this.onAirStatus ) {
							embedPlayer.layoutBuilder.displayAlert( { title: embedPlayer.getKalturaMsg( 'ks-LIVE-STREAM-OFFLINE-TITLE' ), message: embedPlayer.getKalturaMsg( 'ks-LIVE-STREAM-OFFLINE' ), keepOverlay: true } );
						}
					}, _this.getConfig( 'offlineAlertOffest' ) );

				}  else if ( !_this.onAirStatus && onAirObj.onAirStatus ) {
					embedPlayer.layoutBuilder.closeAlert(); //moved from offline to online - hide the offline alert
				}
				_this.onAirStatus = onAirObj.onAirStatus;
				_this.toggleControls( onAirObj.onAirStatus );

				if ( _this.isDVR() ) {
					if ( !onAirObj.onAirStatus ) {
						embedPlayer.triggerHelper('onHideInterfaceComponents', [['liveBackBtn']] );
						if ( _this.shouldHandlePausedMonitor() ) {
							_this.removePausedMonitor();
						}
					} else if ( _this.firstPlay ) {  //show "back to live" button only after first play
						embedPlayer.triggerHelper('onShowInterfaceComponents', [['liveBackBtn']] );
					}
				}
			} );

			this.bind( 'durationChange', function( e, newDuration) {
				if ( _this.switchDone && embedPlayer.isLive() && _this.isDVR() ) {
					//duration should be at least dvrWindow size (with 10% tolerance)
					if ( newDuration < 0.9 * (_this.dvrWindow) ) {
						embedPlayer.setDuration( _this.dvrWindow );
					}
				}
			});

			if ( kWidget.isIOS() ) {
				this.bind( 'timeupdate' , function() {
					var curTime = embedPlayer.getPlayerElementTime();

					// handle timeupdate if pausedTimer was turned on
					if ( _this.dvrTimePassed != 0 ) {
						var lastShownTime = _this.lastShownTime;
						if ( lastShownTime == 0 ) {
							lastShownTime = curTime;
						}
						var accurateTime =  lastShownTime - _this.dvrTimePassed;
						if ( accurateTime < 0 ) {
							accurateTime = 0
						}
						if ( accurateTime > embedPlayer.duration ) {
							accurateTime = embedPlayer.duration;
						}
						_this.updateTimeAndScrubber( accurateTime );

					}
					//handle bug in iOS: currenttime exceeds duration
					else if ( curTime > embedPlayer.duration ) {
						embedPlayer.triggerHelper( 'detachTimeUpdate' );
						embedPlayer.triggerHelper( 'externalTimeUpdate', [ embedPlayer.duration ] );
						_this.lastShownTime =  embedPlayer.duration;
						_this.shouldReAttachTimeUpdate = true;
					}
					else if ( _this.dvrTimePassed == 0 && _this.shouldReAttachTimeUpdate) {
					   _this.sendReAttacheTimeUpdate();
					}
				});
			}

			if ( this.shouldHandlePausedMonitor() ) {

				this.bind( 'onplay', function() {
					if ( _this.isDVR() && _this.switchDone ) {
						//	_this.hideLiveStreamStatus();
						_this.removePausedMonitor();
					}
				} );

				this.bind( 'seeking movingBackToLive', function() {
					//if we are keeping track of the passed time from a previous pause - reset it
					if ( _this.dvrTimePassed != 0 ) {
						_this.dvrTimePassed = 0;
						_this.sendReAttacheTimeUpdate();
					}
				});
			}
		},

		sendReAttacheTimeUpdate: function() {
			this.getPlayer().triggerHelper( 'reattachTimeUpdate' );
			this.lastShownTime = 0;
			this.shouldReAttachTimeUpdate = false
		},

		updateTimeAndScrubber: function( val ) {
			var embedPlayer = this.getPlayer();
			embedPlayer.triggerHelper( 'externalTimeUpdate', [ val ] );
			var playHeadPercent = ( val - embedPlayer.startOffset ) / embedPlayer.duration;
			embedPlayer.triggerHelper( 'externalUpdatePlayHeadPercent', [ playHeadPercent ] );
		},

		isDVR: function(){
			return this.getPlayer().evaluate( '{mediaProxy.entry.dvrStatus}' );
		},

		toggleControls: function( onAirStatus ) {
			if ( onAirStatus && !this.getPlayer().getError()) {
				this.getPlayer().enablePlayControls();
			}  else {
				this.getPlayer().disablePlayControls();
			}
		},

		getCurrentTime: function() {
			return this.getPlayer().getPlayerElement().currentTime;
		},

		removeMinDVRMonitor: function() {
			this.log( "removeMinDVRMonitor" );
			this.minDVRMonitor = clearInterval( this.minDVRMonitor );
		},

		/**
		 * API Requests to update on/off air status
		 */
		addLiveStreamStatusMonitor: function() {
			this.log( "addLiveStreamStatusMonitor" );
			var _this = this;
			this.liveStreamStatusMonitor = setInterval( function() {
				_this.getLiveStreamStatusFromAPI();
			}, _this.liveStreamStatusInterval * 1000 );
		},

		removeLiveStreamStatusMonitor: function() {
			this.log( "removeLiveStreamStatusMonitor" );
			this.liveStreamStatusMonitor = clearInterval( this.liveStreamStatusMonitor );
		},

		/**
		 * indicates if we should handle paused monitor.
		 * relevant only on iOS and if updateIOSPauseTime flag is true
		 */
		shouldHandlePausedMonitor: function() {
			if ( kWidget.isIOS() && this.getConfig('updateIOSPauseTime') ) {
				return true;
			}
			return false;
		},

		/**
		 * Updating display time & scrubber while in paused state
		 */
		addPausedMonitor: function() {
			var _this = this;
			var embedPlayer = this.embedPlayer;
			var vid = embedPlayer.getPlayerElement();
			var pauseTime = _this.lastShownTime;
			if ( pauseTime == 0 ) {
				pauseTime = vid.currentTime;
			}
			var pauseClockTime = Date.now();
			//ignore timeupdate native events, we will calculate the accurate time value and update the timers
			embedPlayer.triggerHelper( 'detachTimeUpdate' );
			this.log( "addPausedMonitor :   Monitor rate = " + mw.getConfig( 'EmbedPlayer.MonitorRate' ) );
			this.pausedMonitor = setInterval( function() {
				var timePassed = ( Date.now() - pauseClockTime ) / 1000;
				var newTime = pauseTime - timePassed;
				if ( newTime >= 0 ) {
					_this.dvrTimePassed = timePassed;
					_this.updateTimeAndScrubber( newTime );
				}
			}, 1000 );
		},

		removePausedMonitor: function() {
			this.log( "removePausedMonitor" );
			this.pausedMonitor = clearInterval( this.pausedMonitor );
		},

		/**
		 * Get on/off air status based on the API and update locally
		 */
		getLiveStreamStatusFromAPI: function( callback ) {
			var _this = this;
			var embedPlayer = this.getPlayer();

			if ( embedPlayer.getFlashvars( 'streamerType') == 'rtmp' ) {
				if ( callback ) {
					callback( _this.onAirStatus );
				}
				return;
			}

			if (this.getConfig("disableLiveCheck")){
				if ( callback ) {
					callback( true );
				}
				embedPlayer.triggerHelper( 'liveStreamStatusUpdate', { 'onAirStatus' : true } );
				return;
			}

			_this.getKalturaClient().doRequest( {
				'service' : 'liveStream',
				'action' : 'islive',
				'id' : embedPlayer.kentryid,
				'protocol' : 'hls',
				'partnerId': embedPlayer.kpartnerid,
				'timestamp' : Date.now()
			}, function( data ) {
				var onAirStatus = false;
				if ( data === true ) {
					onAirStatus = true;
				}
				if ( callback ) {
					callback( onAirStatus );
				}
				embedPlayer.triggerHelper( 'liveStreamStatusUpdate', { 'onAirStatus' : onAirStatus } );
			},mw.getConfig("SkipKSOnIsLiveRequest") );
		},

		getKalturaClient: function() {
			if( ! this.kClient ) {
				this.kClient = mw.kApiGetPartnerClient( this.embedPlayer.kwidgetid );
			}
			return this.kClient;
		},

		log: function( msg ) {
			mw.log( "LiveStream :: " + msg);
		}

	}));

} )( window.mw, window.jQuery );<commit_msg>#PLAT-778 #comment hide duration label if entry is live channel with repeat<commit_after>( function( mw, $ ) {"use strict";

	mw.PluginManager.add( 'liveCore', mw.KBasePlugin.extend({

		firstPlay : false,
		/**
		 * API requests interval for updating live stream status (Seconds).
		 * Default is 30 seconds, to match server's cache expiration
		 */
		liveStreamStatusInterval : 30,

		// Default DVR Window (Seconds)
		defaultDVRWindow : 30 * 60,

		onAirStatus: true,

		dvrTimePassed: 0,

		defaultConfig: {
			//whether to start backwards timer on pause in iOS
			updateIOSPauseTime: false,
			//time in ms to wait before displaying the offline alert
			offlineAlertOffest: 1000,
			//disable the islive check (force live to true)
			disableLiveCheck: false
		},

		/**
		 * indicates the last "current time" displayed. (will be used in iOS - where we sometimes override the current time)
		 */
		lastShownTime: 0,

		/**
		 * (only for iOS) indicates we passed the dvr window size and once we will seek backwards we should reAttach timUpdate events
		 */
		shouldReAttachTimeUpdate: false,

		setup: function() {
			this.addPlayerBindings();
			this.extendApi();
		},
		/**
		 * Extend JS API to match the KDP
		 */
		extendApi: function() {
			var _this = this;

			this.getPlayer().isOffline = function() {
				return !_this.onAirStatus;
			}
		},

		addPlayerBindings: function() {
			var _this = this;
			var embedPlayer = this.getPlayer();

			this.bind( 'playerReady', function() {
				//ui components to hide
				var showComponentsArr = [];
				//ui components to show
				var hideComponentsArr = [];
				hideComponentsArr.push( 'liveBackBtn' );
				_this.dvrTimePassed = 0;
				_this.lastShownTime = 0;

				//live entry
				if ( embedPlayer.isLive() ) {
					_this.addLiveStreamStatusMonitor();
					//hide source selector until we support live streams switching
					hideComponentsArr.push( 'sourceSelector' );
					embedPlayer.disablePlayControls();
					embedPlayer.addPlayerSpinner();
					_this.getLiveStreamStatusFromAPI( function( onAirStatus ) {
						embedPlayer.hideSpinner();
					} );
					_this.switchDone = true;
					if ( embedPlayer.sequenceProxy ) {
						_this.switchDone = false;
					}
					//live + DVR
					if ( _this.isDVR() ) {
						_this.dvrWindow = embedPlayer.evaluate( '{mediaProxy.entry.dvrWindow}' ) * 60;
						if ( !_this.dvrWindow ) {
							_this.dvrWindow = this.defaultDVRWindow;
						}
						if ( kWidget.isIOS() ) {
							embedPlayer.setDuration( _this.dvrWindow );
						}
						if ( embedPlayer.kalturaPlayerMetaData.repeat === true ) {
							hideComponentsArr.push( 'durationLabel' );
						} else {
							showComponentsArr.push( 'durationLabel' );
						}
						showComponentsArr.push( 'scrubber', 'currentTimeLabel' );
					} else {  //live + no DVR
						showComponentsArr.push( 'liveStatus' );
						hideComponentsArr.push( 'scrubber', 'durationLabel', 'currentTimeLabel' );
					}
				}
				//not a live etnry: restore ui, hide live ui
				else {
					hideComponentsArr.push( 'liveStatus' );
					showComponentsArr.push( 'sourceSelector', 'scrubber', 'durationLabel', 'currentTimeLabel' );
					_this.removeLiveStreamStatusMonitor();
				}

				embedPlayer.triggerHelper('onShowInterfaceComponents', [ showComponentsArr ] );
				embedPlayer.triggerHelper('onHideInterfaceComponents', [ hideComponentsArr ] );
			} );

			this.bind( 'onpause', function() {
				if ( _this.isDVR() && _this.switchDone ) {
					embedPlayer.addPlayerSpinner();
					_this.getLiveStreamStatusFromAPI( function( onAirStatus ) {
						if ( onAirStatus ) {
							if ( _this.shouldHandlePausedMonitor() ) {
								_this.addPausedMonitor();
							}
						}
					} );
				}
			} );

			this.bind( 'firstPlay', function() {
				_this.firstPlay = true;
			} );

			this.bind( 'AdSupport_PreSequenceComplete', function() {
				_this.switchDone = true;
			} );

			this.bind( 'liveStreamStatusUpdate', function( e, onAirObj ) {
				//if we moved from live to offline  - show message
				if ( _this.onAirStatus && !onAirObj.onAirStatus ) {
					//simetimes offline is only for a second and the message is not needed..
					setTimeout( function() {
						if ( !_this.onAirStatus ) {
							embedPlayer.layoutBuilder.displayAlert( { title: embedPlayer.getKalturaMsg( 'ks-LIVE-STREAM-OFFLINE-TITLE' ), message: embedPlayer.getKalturaMsg( 'ks-LIVE-STREAM-OFFLINE' ), keepOverlay: true } );
						}
					}, _this.getConfig( 'offlineAlertOffest' ) );

				}  else if ( !_this.onAirStatus && onAirObj.onAirStatus ) {
					embedPlayer.layoutBuilder.closeAlert(); //moved from offline to online - hide the offline alert
				}
				_this.onAirStatus = onAirObj.onAirStatus;
				_this.toggleControls( onAirObj.onAirStatus );

				if ( _this.isDVR() ) {
					if ( !onAirObj.onAirStatus ) {
						embedPlayer.triggerHelper('onHideInterfaceComponents', [['liveBackBtn']] );
						if ( _this.shouldHandlePausedMonitor() ) {
							_this.removePausedMonitor();
						}
					} else if ( _this.firstPlay ) {  //show "back to live" button only after first play
						embedPlayer.triggerHelper('onShowInterfaceComponents', [['liveBackBtn']] );
					}
				}
			} );

			this.bind( 'durationChange', function( e, newDuration) {
				if ( _this.switchDone && embedPlayer.isLive() && _this.isDVR() ) {
					//duration should be at least dvrWindow size (with 10% tolerance)
					if ( newDuration < 0.9 * (_this.dvrWindow) ) {
						embedPlayer.setDuration( _this.dvrWindow );
					}
				}
			});

			if ( kWidget.isIOS() ) {
				this.bind( 'timeupdate' , function() {
					var curTime = embedPlayer.getPlayerElementTime();

					// handle timeupdate if pausedTimer was turned on
					if ( _this.dvrTimePassed != 0 ) {
						var lastShownTime = _this.lastShownTime;
						if ( lastShownTime == 0 ) {
							lastShownTime = curTime;
						}
						var accurateTime =  lastShownTime - _this.dvrTimePassed;
						if ( accurateTime < 0 ) {
							accurateTime = 0
						}
						if ( accurateTime > embedPlayer.duration ) {
							accurateTime = embedPlayer.duration;
						}
						_this.updateTimeAndScrubber( accurateTime );

					}
					//handle bug in iOS: currenttime exceeds duration
					else if ( curTime > embedPlayer.duration ) {
						embedPlayer.triggerHelper( 'detachTimeUpdate' );
						embedPlayer.triggerHelper( 'externalTimeUpdate', [ embedPlayer.duration ] );
						_this.lastShownTime =  embedPlayer.duration;
						_this.shouldReAttachTimeUpdate = true;
					}
					else if ( _this.dvrTimePassed == 0 && _this.shouldReAttachTimeUpdate) {
					   _this.sendReAttacheTimeUpdate();
					}
				});
			}

			if ( this.shouldHandlePausedMonitor() ) {

				this.bind( 'onplay', function() {
					if ( _this.isDVR() && _this.switchDone ) {
						//	_this.hideLiveStreamStatus();
						_this.removePausedMonitor();
					}
				} );

				this.bind( 'seeking movingBackToLive', function() {
					//if we are keeping track of the passed time from a previous pause - reset it
					if ( _this.dvrTimePassed != 0 ) {
						_this.dvrTimePassed = 0;
						_this.sendReAttacheTimeUpdate();
					}
				});
			}
		},

		sendReAttacheTimeUpdate: function() {
			this.getPlayer().triggerHelper( 'reattachTimeUpdate' );
			this.lastShownTime = 0;
			this.shouldReAttachTimeUpdate = false
		},

		updateTimeAndScrubber: function( val ) {
			var embedPlayer = this.getPlayer();
			embedPlayer.triggerHelper( 'externalTimeUpdate', [ val ] );
			var playHeadPercent = ( val - embedPlayer.startOffset ) / embedPlayer.duration;
			embedPlayer.triggerHelper( 'externalUpdatePlayHeadPercent', [ playHeadPercent ] );
		},

		isDVR: function(){
			return this.getPlayer().evaluate( '{mediaProxy.entry.dvrStatus}' );
		},

		toggleControls: function( onAirStatus ) {
			if ( onAirStatus && !this.getPlayer().getError()) {
				this.getPlayer().enablePlayControls();
			}  else {
				this.getPlayer().disablePlayControls();
			}
		},

		getCurrentTime: function() {
			return this.getPlayer().getPlayerElement().currentTime;
		},

		removeMinDVRMonitor: function() {
			this.log( "removeMinDVRMonitor" );
			this.minDVRMonitor = clearInterval( this.minDVRMonitor );
		},

		/**
		 * API Requests to update on/off air status
		 */
		addLiveStreamStatusMonitor: function() {
			this.log( "addLiveStreamStatusMonitor" );
			var _this = this;
			this.liveStreamStatusMonitor = setInterval( function() {
				_this.getLiveStreamStatusFromAPI();
			}, _this.liveStreamStatusInterval * 1000 );
		},

		removeLiveStreamStatusMonitor: function() {
			this.log( "removeLiveStreamStatusMonitor" );
			this.liveStreamStatusMonitor = clearInterval( this.liveStreamStatusMonitor );
		},

		/**
		 * indicates if we should handle paused monitor.
		 * relevant only on iOS and if updateIOSPauseTime flag is true
		 */
		shouldHandlePausedMonitor: function() {
			if ( kWidget.isIOS() && this.getConfig('updateIOSPauseTime') ) {
				return true;
			}
			return false;
		},

		/**
		 * Updating display time & scrubber while in paused state
		 */
		addPausedMonitor: function() {
			var _this = this;
			var embedPlayer = this.embedPlayer;
			var vid = embedPlayer.getPlayerElement();
			var pauseTime = _this.lastShownTime;
			if ( pauseTime == 0 ) {
				pauseTime = vid.currentTime;
			}
			var pauseClockTime = Date.now();
			//ignore timeupdate native events, we will calculate the accurate time value and update the timers
			embedPlayer.triggerHelper( 'detachTimeUpdate' );
			this.log( "addPausedMonitor :   Monitor rate = " + mw.getConfig( 'EmbedPlayer.MonitorRate' ) );
			this.pausedMonitor = setInterval( function() {
				var timePassed = ( Date.now() - pauseClockTime ) / 1000;
				var newTime = pauseTime - timePassed;
				if ( newTime >= 0 ) {
					_this.dvrTimePassed = timePassed;
					_this.updateTimeAndScrubber( newTime );
				}
			}, 1000 );
		},

		removePausedMonitor: function() {
			this.log( "removePausedMonitor" );
			this.pausedMonitor = clearInterval( this.pausedMonitor );
		},

		/**
		 * Get on/off air status based on the API and update locally
		 */
		getLiveStreamStatusFromAPI: function( callback ) {
			var _this = this;
			var embedPlayer = this.getPlayer();

			if ( embedPlayer.getFlashvars( 'streamerType') == 'rtmp' ) {
				if ( callback ) {
					callback( _this.onAirStatus );
				}
				return;
			}

			if (this.getConfig("disableLiveCheck")){
				if ( callback ) {
					callback( true );
				}
				embedPlayer.triggerHelper( 'liveStreamStatusUpdate', { 'onAirStatus' : true } );
				return;
			}

			_this.getKalturaClient().doRequest( {
				'service' : 'liveStream',
				'action' : 'islive',
				'id' : embedPlayer.kentryid,
				'protocol' : 'hls',
				'partnerId': embedPlayer.kpartnerid,
				'timestamp' : Date.now()
			}, function( data ) {
				var onAirStatus = false;
				if ( data === true ) {
					onAirStatus = true;
				}
				if ( callback ) {
					callback( onAirStatus );
				}
				embedPlayer.triggerHelper( 'liveStreamStatusUpdate', { 'onAirStatus' : onAirStatus } );
			},mw.getConfig("SkipKSOnIsLiveRequest") );
		},

		getKalturaClient: function() {
			if( ! this.kClient ) {
				this.kClient = mw.kApiGetPartnerClient( this.embedPlayer.kwidgetid );
			}
			return this.kClient;
		},

		log: function( msg ) {
			mw.log( "LiveStream :: " + msg);
		}

	}));

} )( window.mw, window.jQuery );<|endoftext|>


<commit_before>( function( mw, $ ) {"use strict";

// Class defined in resources/class/class.js
mw.KBasePlugin = Class.extend({
	asyncInit: false,
	init: function( embedPlayer, callback, pluginName ){

		// Save to local scope
		this.embedPlayer = embedPlayer;
		this.initCompleteCallback = callback;
		this.pluginName = pluginName;

		this.bindPostFix = '.' + pluginName;

		this.setDefaults();
		if( !this.isSafeEnviornment() ) {
			this.initCompleteCallback();
			return false;
		}

		// Call plugin setup method
		this.setup();

		// Run initCompleteCallback
		if( this.asyncInit === false ) {
			this.initCompleteCallback();
		}

		return this;
	},
	setDefaults: function(){
		var _this = this;
		// Set default configuration for the plugin
		if( $.isPlainObject(this.defaultConfig) ) {
			$.each( this.defaultConfig, function( key, value ) {
				if( _this.getConfig( key ) === undefined ) {
					_this.setConfig( key, value );
				}
			});
		}
	},
	isSafeEnviornment: function(){
		return true;
	},
	setup: function() {},
	getPlayer: function() {
		return this.embedPlayer;
	},
	getConfig: function( attr ) {
		return this.embedPlayer.getKalturaConfig( this.pluginName, attr );
	},
	setConfig: function( attr, value ) {
		this.embedPlayer.setKalturaConfig( this.pluginName, attr, value );
	},
	bind: function( eventName, callback ){
		return this.embedPlayer.bindHelper( eventName + this.bindPostFix, callback);
	},
	unbind: function( eventName ){
		eventName += this.bindPostFix;
		return this.embedPlayer.unbindHelper( eventName );
	}
});

} )( window.mw, window.jQuery );<commit_msg>Added log method<commit_after>( function( mw, $ ) {"use strict";

// Class defined in resources/class/class.js
mw.KBasePlugin = Class.extend({
	asyncInit: false,
	init: function( embedPlayer, callback, pluginName ){

		// Save to local scope
		this.embedPlayer = embedPlayer;
		this.initCompleteCallback = callback;
		this.pluginName = pluginName;

		this.bindPostFix = '.' + pluginName;

		this.setDefaults();
		if( !this.isSafeEnviornment() ) {
			this.initCompleteCallback();
			return false;
		}

		// Call plugin setup method
		this.setup();

		// Run initCompleteCallback
		if( this.asyncInit === false ) {
			this.initCompleteCallback();
		}

		return this;
	},
	setDefaults: function(){
		var _this = this;
		// Set default configuration for the plugin
		if( $.isPlainObject(this.defaultConfig) ) {
			$.each( this.defaultConfig, function( key, value ) {
				if( _this.getConfig( key ) === undefined ) {
					_this.setConfig( key, value );
				}
			});
		}
	},
	isSafeEnviornment: function(){
		return true;
	},
	setup: function() {},
	getPlayer: function() {
		return this.embedPlayer;
	},
	getConfig: function( attr ) {
		return this.embedPlayer.getKalturaConfig( this.pluginName, attr );
	},
	setConfig: function( attr, value ) {
		this.embedPlayer.setKalturaConfig( this.pluginName, attr, value );
	},
	bind: function( eventName, callback ){
		return this.embedPlayer.bindHelper( eventName + this.bindPostFix, callback);
	},
	unbind: function( eventName ){
		eventName += this.bindPostFix;
		return this.embedPlayer.unbindHelper( eventName );
	},
	log: function( msg ){
		mw.log( this.pluginName + '::' + msg );
	}
});

} )( window.mw, window.jQuery );<|endoftext|>


<commit_before>/*jshint esversion: 6 */

var config = require('./config');
var express = require('express');
var bodyParser = require('body-parser');
var TelegramBot = require('telegrambot');
var Google = require('google');

// TELEGRAM SETUP
var api = new TelegramBot(config.TELEGRAM_TOKEN);
api.setWebhook({url: config.WEBHOOK_BASE_URL+config.WEBHOOK_PATH}, function(err, message) {
  if (err) {
    console.log(err);
  } else {
    console.log('Telegram webhook set');
  }
});

// SERVER SETUP
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', function status(req, res, next) {
  res.json({ status: 'UP' });
});
app.post(config.WEBHOOK_PATH, function(req, res) {
  if (!req.hasOwnProperty('body')) {
    return res.send();
  }
  var body = req.body;
  if (body.hasOwnProperty('message')) {
    readCommand(body.message);
  }
  res.send();
});

// Start server
var server = app.listen(config.SERVER_PORT, function () {
  var host = server.address().address;
  var port = server.address().port;
  console.log('Server listening at http://%s:%s', host, port);
});

var sendError = function(errorMessage, chatId) {
  api.sendMessage({ chat_id: chatId, text: errorMessage }, function (err, message) {
    if (err) {
      console.log(err);
    }
  });
};

var readCommand = function(message) {
  console.log('Reading command...');
  if (message) {
    if (message.text !== undefined) {
      if (message.text === '/start') {
        api.sendMessage({ chat_id: message.chat.id, text: config.START_MESSAGE }, function (err, message) {
          if (err) {
            console.log(err);
          }
        });
      } else if (message.text.startsWith('/search')) {
        var query = message.text.replace('/search', '');
        if (query.length > 0) {
          Google(query + ' site:scaruffi.com', function (err, res) {
            if (err) {
              console.error(err);
              api.sendMessage({ chat_id: message.chat.id, text: config.ERROR_MESSAGE_EMPTY_RESP }, function (err, message) {
                if (err) {
                  console.log(err);
                }
              });
              return;
            }
            if (!res.links) {
              console.error('Error: links not found in response.');
              api.sendMessage({ chat_id: message.chat.id, text: config.ERROR_MESSAGE_EMPTY_RESP }, function (err, message) {
                if (err) {
                  console.log(err);
                }
              });
              return;
            }
            var link = res.links[0];
            console.log(link);
            if (!link || !link.href) {
              api.sendMessage({ chat_id: message.chat.id, text: config.ERROR_MESSAGE_EMPTY_RESP }, function (err, message) {
                if (err) {
                  console.log(err);
                }
              });
              return;
            } else {
              api.sendMessage({ chat_id: message.chat.id, text: link.href }, function (err, message) {
                if (err) {
                  console.log(err);
                }
              });
            }
          });
        } else {
          api.sendMessage({ chat_id: message.chat.id, text: config.ERROR_MESSAGE_EMPTY_QUERY }, function (err, message) {
            if (err) {
              console.log(err);
            }
          });
        }
      }
    } else {
      console.log('Message text missing');
    }
  } else {
    console.log('Message missing');
  }
  console.log('Command processed.');
};
<commit_msg>Added inline query support.<commit_after>/*jshint esversion: 6 */

var config = require('./config');
var express = require('express');
var bodyParser = require('body-parser');
var TelegramBot = require('telegrambot');
var Google = require('google');
Google.resultsPerPage = 5;

// TELEGRAM SETUP
var api = new TelegramBot(config.TELEGRAM_TOKEN);
api.setWebhook({url: config.WEBHOOK_BASE_URL+config.WEBHOOK_PATH}, function(err, message) {
  if (err) {
    console.log(err);
  } else {
    console.log('Telegram webhook set');
  }
});

// SERVER SETUP
var app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.get('/', function status(req, res, next) {
  res.json({ status: 'UP' });
});
app.post(config.WEBHOOK_PATH, function(req, res) {
  if (!req.hasOwnProperty('body')) {
    return res.send();
  }
  var body = req.body;
  if (body.hasOwnProperty('message')) {
    readCommand(body.message);
  } else if (body.hasOwnProperty('inline_query')) {
    bot.readInlineQuery(body.inline_query);
  }
  res.send();
});

// Start server
var server = app.listen(config.SERVER_PORT, function () {
  var host = server.address().address;
  var port = server.address().port;
  console.log('Server listening at http://%s:%s', host, port);
});

var readInlineQuery = function(query) {
  console.log('Reading query...');
  if (query.query.length > 0) {
    Google(query.query + ' site:scaruffi.com', function (err, res) {
      if (err) {
        console.error(err);
        return;
      }
      if (!res.links) {
        console.error('Error: links not found in response.');
        return;
      }
      var results = res.links.map((link, index) => {
        return {
          'type': 'article',
          'id': 'id'+index,
          'title': link.title,
          'input_message_content': {
            'message_text': link.description
          },
          'url': link.href,
        };
      });
      if (!results) {
        console.error('Error: links not found in response.');
        return;
      } else {
        api.answerInlineQuery({ inline_query_id: query.id, results: results }, function (err, message) {
          if (err) {
            console.log(err);
          }
        });
      }
    });
  }
  console.log('Query read.');
};

var readCommand = function(message) {
  console.log('Reading command...');
  if (message) {
    if (message.text !== undefined) {
      if (message.text === '/start') {
        api.sendMessage({ chat_id: message.chat.id, text: config.START_MESSAGE }, function (err, message) {
          if (err) {
            console.error(err);
          }
        });
      } else if (message.text.startsWith('/search')) {
        var query = message.text.replace('/search', '');
        if (query.length > 0) {
          Google(query + ' site:scaruffi.com', function (err, res) {
            if (err) {
              console.error(err);
              api.sendMessage({ chat_id: message.chat.id, text: config.ERROR_MESSAGE_EMPTY_RESP }, function (err, message) {
                if (err) {
                  console.error(err);
                }
              });
              return;
            }
            if (!res.links) {
              console.error('Error: links not found in response.');
              api.sendMessage({ chat_id: message.chat.id, text: config.ERROR_MESSAGE_EMPTY_RESP }, function (err, message) {
                if (err) {
                  console.error(err);
                }
              });
              return;
            }
            var link = res.links[0];
            if (!link || !link.href) {
              api.sendMessage({ chat_id: message.chat.id, text: config.ERROR_MESSAGE_EMPTY_RESP }, function (err, message) {
                if (err) {
                  console.error(err);
                }
              });
              return;
            } else {
              api.sendMessage({ chat_id: message.chat.id, text: link.href }, function (err, message) {
                if (err) {
                  console.error(err);
                }
              });
            }
          });
        } else {
          api.sendMessage({ chat_id: message.chat.id, text: config.ERROR_MESSAGE_EMPTY_QUERY }, function (err, message) {
            if (err) {
              console.error(err);
            }
          });
        }
      }
    } else {
      console.error('Message text missing');
    }
  } else {
    console.error('Message missing');
  }
  console.log('Command processed.');
};
<|endoftext|>


<commit_before>/**
 * ### .get(obj, path)
 *
 * Retrieve the value in an object given a string path.
 *
 * ```js
 * var obj = {
 *     prop1: {
 *         arr: ['a', 'b', 'c']
 *       , str: 'Hello'
 *     }
 *   , prop2: {
 *         arr: [ { nested: 'Universe' } ]
 *       , str: 'Hello again!'
 *     }
 * };
 * ```
 *
 * The following would be the results.
 *
 * ```js
 * var properties = require('tea-properties');
 * properties.get(obj, 'prop1.str'); // Hello
 * properties.get(obj, 'prop1.att[2]'); // b
 * properties.get(obj, 'prop2.arr[0].nested'); // Universe
 * ```
 *
 * @param {Object} object
 * @param {String} path
 * @return {Object} value or `undefined`
 */

exports.get = function(obj, path) {
  var parsed = exports.parse(path);
  return getPathValue(parsed, obj);
};

/**
 * ### .set(path, value, object)
 *
 * Define the value in an object at a given string path.
 *
 * ```js
 * var obj = {
 *     prop1: {
 *         arr: ['a', 'b', 'c']
 *       , str: 'Hello'
 *     }
 *   , prop2: {
 *         arr: [ { nested: 'Universe' } ]
 *       , str: 'Hello again!'
 *     }
 * };
 * ```
 *
 * The following would be acceptable.
 *
 * ```js
 * var properties = require('tea-properties');
 * properties.set(obj, 'prop1.str', 'Hello Universe!');
 * properties.set(obj, 'prop1.arr[2]', 'B');
 * properties.set(obj, 'prop2.arr[0].nested.value', { hello: 'universe' });
 * ```
 *
 * @param {Object} object
 * @param {String} path
 * @param {Mixed} value
 * @api public
 */

exports.set = function(obj, path, val) {
  var parsed = exports.parse(path);
  setPathValue(parsed, val, obj);
};

/*!
 * Helper function used to parse string object
 * paths. Use in conjunction with `getPathValue`.
 *
 *  var parsed = parsePath('myobject.property.subprop');
 *
 * ### Paths:
 *
 * * Can be as near infinitely deep and nested
 * * Arrays are also valid using the formal `myobject.document[3].property`.
 *
 * @param {String} path
 * @returns {Array} parsed
 */

exports.parse = function(path) {
  var str = (path || '').replace(/\[/g, '.[');
  var parts = str.match(/(\\\.|[^.]+?)+/g);

  return parts.map(function(value) {
    var re = /\[(\d+)\]$/
      , mArr = re.exec(value)
    if (mArr) return { i: parseFloat(mArr[1]) };
    else return { p: value };
  });
};

/*!
 * Companion function for `parsePath` that returns
 * the value located at the parsed address.
 *
 *  var value = getPathValue(parsed, obj);
 *
 * @param {Object} parsed definition from `parsePath`.
 * @param {Object} object to search against
 * @returns {Object|Undefined} value
 */

function getPathValue(parsed, obj) {
  var tmp = obj;
  var res;

  for (var i = 0, l = parsed.length; i < l; i++) {
    var part = parsed[i];
    if (tmp) {
      if (defined(part.p)) tmp = tmp[part.p];
      else if (defined(part.i)) tmp = tmp[part.i];
      if (i == (l - 1)) res = tmp;
    } else {
      res = undefined;
    }
  }

  return res;
};

/*!
 * Companion function for `parsePath` that sets
 * the value located at a parsed address.
 *
 *  setPathValue(parsed, 'value', obj);
 *
 * @param {Object} parsed definition from `parsePath`
 * @param {*} value to use upon set
 * @param {Object} object to search and define on
 * @api private
 */

function setPathValue(parsed, val, obj) {
  var tmp = obj;
  var i = 0;
  var l = parsed.length;
  var part;

  for (; i < l; i++) {
    part = parsed[i];

    if (defined(tmp) && i == (l - 1)) {
      var x = defined(part.p) ? part.p : part.i;
      tmp[x] = val;
    } else if (defined(tmp)) {
      if (defined(part.p) && tmp[part.p]) {
        tmp = tmp[part.p];
      } else if (defined(part.i) && tmp[part.i]) {
        tmp = tmp[part.i];
      } else {
        var next = parsed[i + 1];
        var x = defined(part.p) ? part.p : part.i;
        var y = defined(next.p) ? {} : [];
        tmp[x] = y;
        tmp = tmp[x];
      }
    } else {
      if (i == (l - 1)) tmp = val;
      else if (defined(part.p)) tmp = {};
      else if (defined(part.i)) tmp = [];
    }
  }
};

/*!
 * Check if `val` is defined.
 *
 * @param {Mixed} val
 * @returns {Boolean} `true` if defined
 * @api private
 */

function defined(val) {
  return !(!val && 'undefined' === typeof val);
}
<commit_msg>Do not use Array#map<commit_after>/**
 * ### .get(obj, path)
 *
 * Retrieve the value in an object given a string path.
 *
 * ```js
 * var obj = {
 *     prop1: {
 *         arr: ['a', 'b', 'c']
 *       , str: 'Hello'
 *     }
 *   , prop2: {
 *         arr: [ { nested: 'Universe' } ]
 *       , str: 'Hello again!'
 *     }
 * };
 * ```
 *
 * The following would be the results.
 *
 * ```js
 * var properties = require('tea-properties');
 * properties.get(obj, 'prop1.str'); // Hello
 * properties.get(obj, 'prop1.att[2]'); // b
 * properties.get(obj, 'prop2.arr[0].nested'); // Universe
 * ```
 *
 * @param {Object} object
 * @param {String} path
 * @return {Object} value or `undefined`
 */

exports.get = function(obj, path) {
  var parsed = exports.parse(path);
  return getPathValue(parsed, obj);
};

/**
 * ### .set(path, value, object)
 *
 * Define the value in an object at a given string path.
 *
 * ```js
 * var obj = {
 *     prop1: {
 *         arr: ['a', 'b', 'c']
 *       , str: 'Hello'
 *     }
 *   , prop2: {
 *         arr: [ { nested: 'Universe' } ]
 *       , str: 'Hello again!'
 *     }
 * };
 * ```
 *
 * The following would be acceptable.
 *
 * ```js
 * var properties = require('tea-properties');
 * properties.set(obj, 'prop1.str', 'Hello Universe!');
 * properties.set(obj, 'prop1.arr[2]', 'B');
 * properties.set(obj, 'prop2.arr[0].nested.value', { hello: 'universe' });
 * ```
 *
 * @param {Object} object
 * @param {String} path
 * @param {Mixed} value
 * @api public
 */

exports.set = function(obj, path, val) {
  var parsed = exports.parse(path);
  setPathValue(parsed, val, obj);
};

/*!
 * Helper function used to parse string object
 * paths. Use in conjunction with `getPathValue`.
 *
 *  var parsed = parsePath('myobject.property.subprop');
 *
 * ### Paths:
 *
 * * Can be as near infinitely deep and nested
 * * Arrays are also valid using the formal `myobject.document[3].property`.
 *
 * @param {String} path
 * @returns {Array} parsed
 */

exports.parse = function(path) {
  var str = (path || '').replace(/\[/g, '.[');
  var parts = str.match(/(\\\.|[^.]+?)+/g);
  var re = /\[(\d+)\]$/;
  var ret = [];
  var mArr = null;

  for (var i = 0, len = parts.length; i < len; i++) {
    mArr = re.exec(parts[i]);
    ret.push(mArr ? { i: parseFloat(mArr[1]) } : { p: parts[i] });
  }

  return ret;
};

/*!
 * Companion function for `parsePath` that returns
 * the value located at the parsed address.
 *
 *  var value = getPathValue(parsed, obj);
 *
 * @param {Object} parsed definition from `parsePath`.
 * @param {Object} object to search against
 * @returns {Object|Undefined} value
 */

function getPathValue(parsed, obj) {
  var tmp = obj;
  var res;

  for (var i = 0, l = parsed.length; i < l; i++) {
    var part = parsed[i];
    if (tmp) {
      if (defined(part.p)) tmp = tmp[part.p];
      else if (defined(part.i)) tmp = tmp[part.i];
      if (i == (l - 1)) res = tmp;
    } else {
      res = undefined;
    }
  }

  return res;
};

/*!
 * Companion function for `parsePath` that sets
 * the value located at a parsed address.
 *
 *  setPathValue(parsed, 'value', obj);
 *
 * @param {Object} parsed definition from `parsePath`
 * @param {*} value to use upon set
 * @param {Object} object to search and define on
 * @api private
 */

function setPathValue(parsed, val, obj) {
  var tmp = obj;
  var i = 0;
  var l = parsed.length;
  var part;

  for (; i < l; i++) {
    part = parsed[i];

    if (defined(tmp) && i == (l - 1)) {
      var x = defined(part.p) ? part.p : part.i;
      tmp[x] = val;
    } else if (defined(tmp)) {
      if (defined(part.p) && tmp[part.p]) {
        tmp = tmp[part.p];
      } else if (defined(part.i) && tmp[part.i]) {
        tmp = tmp[part.i];
      } else {
        var next = parsed[i + 1];
        var x = defined(part.p) ? part.p : part.i;
        var y = defined(next.p) ? {} : [];
        tmp[x] = y;
        tmp = tmp[x];
      }
    } else {
      if (i == (l - 1)) tmp = val;
      else if (defined(part.p)) tmp = {};
      else if (defined(part.i)) tmp = [];
    }
  }
};

/*!
 * Check if `val` is defined.
 *
 * @param {Mixed} val
 * @returns {Boolean} `true` if defined
 * @api private
 */

function defined(val) {
  return !(!val && 'undefined' === typeof val);
}
<|endoftext|>


<commit_before>const util = require('util');
const assert = require('assert');
const Writable = require('stream').Writable;

const retry = require('retry');
const AWS = require('aws-sdk');
const merge = require('lodash.merge');

/**
 * [KinesisStream description]
 * @param {Object} params
 * @param {string} [params.accessKeyId] AWS credentials
 * @param {string} [params.secretAccessKey] AWS credential
 * @param {string} [params.region] AWS region
 * @param {string} params.streamName AWS Knesis stream name
 * @param {function} params.partitionKey function that return the partitionKey based on a msg passed by argument
 * @param {object} [params.httpOptions={}] HTTP options that will be used on `aws-sdk` (e.g. timeout values)
 * @param {number} [params.buffer.timeout] Max. number of seconds
 *                                         to wait before send msgs to stream
 * @param {number} [params.buffer.length] Max. number of msgs to queue
 *                                        before send them to stream.
 * @param {@function} [params.buffer.isPrioritaryMsg] Evaluates a message and returns true if msg has priority (to be deprecated)
 * @param {@function} [params.buffer.hasPriority] Evaluates a message and returns true if msg has priority
 * @param {@function} [params.buffer.retry.retries] Attempts to be made to flush a batch
 * @param {@function} [params.buffer.retry.minTimeout] Min time to wait between attempts
 * @param {@function} [params.buffer.retry.maxTimeout] Max time to wait between attempts
 */

const defaultBuffer = {
  timeout: 5,
  length: 10,
  hasPriority: function() {
    return false;
  },
  retry: {
    retries: 2,
    minTimeout: 300,
    maxTimeout: 500
  }
};

function KinesisStream (params) {
  assert(params.streamName, 'streamName required');

  this.streamName = params.streamName;
  this.buffer = merge(defaultBuffer, params.buffer);
  this.partitionKey = params.partitionKey || function getPartitionKey() {
    return Date.now().toString();
  };

  this.hasPriority = this.buffer.isPrioritaryMsg || this.buffer.hasPriority;

  // increase the timeout to get credentials from the EC2 Metadata Service
  AWS.config.credentials = new AWS.EC2MetadataCredentials({
    httpOptions: params.httpOptions || { timeout: 5000 }
  });

  this.recordsQueue = [];

  this.kinesis = params.kinesis || new AWS.Kinesis({
    accessKeyId: params.accessKeyId,
    secretAccessKey: params.secretAccessKey,
    region: params.region,
    httpOptions: params.httpOptions
  });

  Writable.call(this, { objectMode: params.objectMode });
}

util.inherits(KinesisStream, Writable);

function parseChunk(chunk) {
  if (Buffer.isBuffer(chunk) ) {
    chunk = chunk.toString();
  }
  if (typeof chunk === 'string') {
    chunk = JSON.parse(chunk);
  }
  return chunk;
}

KinesisStream.prototype._write = function(chunk, enc, next) {
  chunk = parseChunk(chunk);

  const hasPriority = this.hasPriority(chunk);
  if (hasPriority) {
    this.recordsQueue.unshift(chunk);
  } else {
    this.recordsQueue.push(chunk);
  }

  if (this.timer) {
    clearTimeout(this.timer);
  }

  if (this.recordsQueue.length >= this.buffer.length || hasPriority) {
    this.flush();
  } else {
    this.timer = setTimeout(this.flush.bind(this), this.buffer.timeout * 1000);
  }

  return next();
};

KinesisStream.prototype.dispatch = function(records, cb) {
  if (records.length === 0) {
    return cb ? cb() : null;
  }

  const operation = retry.operation(this.buffer.retry);

  const partitionKey = this.partitionKey();

  const formattedRecords = records.map((record) => {
    return { Data: JSON.stringify(record), PartitionKey: partitionKey };
  });

  operation.attempt(() => {
    this.putRecords(formattedRecords, (err) => {
      if (operation.retry(err)) {
        return;
      }

      if (err) {
        this.emitRecordError(err, records);
      }

      if (cb) {
        return cb(err ? operation.mainError() : null);
      }
    });
  });
};

KinesisStream.prototype.putRecords = function(records, cb) {
  const req = this.kinesis.putRecords({
    StreamName: this.streamName,
    Records: records
  }, cb);

  // remove all listeners which end up leaking
  req.on('complete', function() {
    req.removeAllListeners();
    req.response.httpResponse.stream && req.response.httpResponse.stream.removeAllListeners();
    req.httpRequest.stream && req.httpRequest.stream.removeAllListeners();
  });
};

KinesisStream.prototype.flush = function() {
  this.dispatch(this.recordsQueue.splice(0, this.buffer.length));
};

KinesisStream.prototype.emitRecordError = function (err, records) {
  err.records = records;
  this.emit('error', err);
};

module.exports = KinesisStream;
<commit_msg>Fix partitionKey to match documentation<commit_after>const util = require('util');
const assert = require('assert');
const Writable = require('stream').Writable;

const retry = require('retry');
const AWS = require('aws-sdk');
const merge = require('lodash.merge');

/**
 * [KinesisStream description]
 * @param {Object} params
 * @param {string} [params.accessKeyId] AWS credentials
 * @param {string} [params.secretAccessKey] AWS credential
 * @param {string} [params.region] AWS region
 * @param {string} params.streamName AWS Knesis stream name
 * @param {function} params.partitionKey function that return the partitionKey based on a msg passed by argument
 * @param {object} [params.httpOptions={}] HTTP options that will be used on `aws-sdk` (e.g. timeout values)
 * @param {number} [params.buffer.timeout] Max. number of seconds
 *                                         to wait before send msgs to stream
 * @param {number} [params.buffer.length] Max. number of msgs to queue
 *                                        before send them to stream.
 * @param {@function} [params.buffer.isPrioritaryMsg] Evaluates a message and returns true if msg has priority (to be deprecated)
 * @param {@function} [params.buffer.hasPriority] Evaluates a message and returns true if msg has priority
 * @param {@function} [params.buffer.retry.retries] Attempts to be made to flush a batch
 * @param {@function} [params.buffer.retry.minTimeout] Min time to wait between attempts
 * @param {@function} [params.buffer.retry.maxTimeout] Max time to wait between attempts
 */

const defaultBuffer = {
  timeout: 5,
  length: 10,
  hasPriority: function() {
    return false;
  },
  retry: {
    retries: 2,
    minTimeout: 300,
    maxTimeout: 500
  }
};

function KinesisStream (params) {
  assert(params.streamName, 'streamName required');

  this.streamName = params.streamName;
  this.buffer = merge(defaultBuffer, params.buffer);
  this.partitionKey = params.partitionKey || function getPartitionKey() {
    return Date.now().toString();
  };

  this.hasPriority = this.buffer.isPrioritaryMsg || this.buffer.hasPriority;

  // increase the timeout to get credentials from the EC2 Metadata Service
  AWS.config.credentials = new AWS.EC2MetadataCredentials({
    httpOptions: params.httpOptions || { timeout: 5000 }
  });

  this.recordsQueue = [];

  this.kinesis = params.kinesis || new AWS.Kinesis({
    accessKeyId: params.accessKeyId,
    secretAccessKey: params.secretAccessKey,
    region: params.region,
    httpOptions: params.httpOptions
  });

  Writable.call(this, { objectMode: params.objectMode });
}

util.inherits(KinesisStream, Writable);

function parseChunk(chunk) {
  if (Buffer.isBuffer(chunk) ) {
    chunk = chunk.toString();
  }
  if (typeof chunk === 'string') {
    chunk = JSON.parse(chunk);
  }
  return chunk;
}

KinesisStream.prototype._write = function(chunk, enc, next) {
  chunk = parseChunk(chunk);

  const hasPriority = this.hasPriority(chunk);
  if (hasPriority) {
    this.recordsQueue.unshift(chunk);
  } else {
    this.recordsQueue.push(chunk);
  }

  if (this.timer) {
    clearTimeout(this.timer);
  }

  if (this.recordsQueue.length >= this.buffer.length || hasPriority) {
    this.flush();
  } else {
    this.timer = setTimeout(this.flush.bind(this), this.buffer.timeout * 1000);
  }

  return next();
};

KinesisStream.prototype.dispatch = function(records, cb) {
  if (records.length === 0) {
    return cb ? cb() : null;
  }

  const operation = retry.operation(this.buffer.retry);

  const formattedRecords = records.map((record) => {
    const partitionKey = typeof this.partitionKey === 'function'
	  ? this.partitionKey(record)
	  : this.partitionKey;
    return { Data: JSON.stringify(record), PartitionKey: partitionKey };
  });

  operation.attempt(() => {
    this.putRecords(formattedRecords, (err) => {
      if (operation.retry(err)) {
        return;
      }

      if (err) {
        this.emitRecordError(err, records);
      }

      if (cb) {
        return cb(err ? operation.mainError() : null);
      }
    });
  });
};

KinesisStream.prototype.putRecords = function(records, cb) {
  const req = this.kinesis.putRecords({
    StreamName: this.streamName,
    Records: records
  }, cb);

  // remove all listeners which end up leaking
  req.on('complete', function() {
    req.removeAllListeners();
    req.response.httpResponse.stream && req.response.httpResponse.stream.removeAllListeners();
    req.httpRequest.stream && req.httpRequest.stream.removeAllListeners();
  });
};

KinesisStream.prototype.flush = function() {
  this.dispatch(this.recordsQueue.splice(0, this.buffer.length));
};

KinesisStream.prototype.emitRecordError = function (err, records) {
  err.records = records;
  this.emit('error', err);
};

module.exports = KinesisStream;
<|endoftext|>


<commit_before>/*!
 * index
 * Date: 2016/6/21
 * https://github.com/Nuintun/file-send
 *
 * This is licensed under the MIT License (MIT).
 * For details, see: https://github.com/Nuintun/file-send/blob/master/LICENSE
 */

'use strict';

// external lib
var ms = require('ms');
var path = require('path');
var http = require('http');
var fresh = require('fresh');
var util = require('./lib/util');
var parseUrl = require('url').parse;
var micromatch = require('micromatch');
var through = require('./lib/through');
var escapeHtml = require('escape-html');
var EventEmitter = require('events').EventEmitter;

// variable declaration
var CWD = process.cwd(); // current working directory
var MAXMAXAGE = 60 * 60 * 24 * 365; // the max max-age set

var listenerCount = EventEmitter.listenerCount
  || function (emitter, type){ return emitter.listeners(type).length; };

function FileSend(request, options){
  if (!(this instanceof FileSend)) {
    return new FileSend(request, options);
  }

  if (!(request instanceof http.IncomingMessage)) {
    throw new TypeError('The first argument must be a http request.');
  }

  this.headers = {};
  this.request = request;
  this.method = this.request.method;

  options = options || {};

  var url, path, root, etag, ignore, ignoreAccess, maxAge, lastModified, stream;

  // url
  util.defineProperty(this, 'url', {
    enumerable: true,
    get: function (){
      if (!url) {
        url = util.decodeURI(request.url);
        url = -1 ? url : util.posixPath(url);
      }

      return url;
    }
  });

  // path
  util.defineProperty(this, 'path', {
    enumerable: true,
    get: function (){
      if (!path) {
        path = this.url === -1
          ? url
          : parseUrl(this.url).pathname;
      }

      return path;
    }
  });

  // root
  util.defineProperty(this, 'root', {
    enumerable: true,
    get: function (){
      if (!root) {
        root = util.isType(options.root, 'string')
          ? path.resolve(options.root)
          : CWD;

        root = util.posixPath(root + path.sep);
      }

      return root;
    }
  });

  // etag
  util.defineProperty(this, 'etag', {
    enumerable: true,
    get: function (){
      if (!etag) {
        etag = options.etag !== undefined
          ? Boolean(options.etag)
          : true;
      }

      return etag;
    }
  });

  // ignore
  util.defineProperty(this, 'ignore', {
    enumerable: true,
    get: function (){
      if (!ignore) {
        if (this.ignoreAccess !== 'allow') {
          ignore = Array.isArray(options.ignore)
            ? options.ignore
            : [options.ignore];

          ignore = ignore.filter(function (pattern){
            return util.isType(pattern, 'string')
              || util.isType(pattern, 'regexp')
              || util.isType(pattern, 'function');
          });
        } else {
          ignore = [];
        }
      }

      return ignore;
    }
  });

  // ignore-access
  util.defineProperty(this, 'ignoreAccess', {
    enumerable: true,
    get: function (){
      if (!ignoreAccess) {
        switch (options.ignoreAccess) {
          case 'allow':
          case 'deny':
          case 'ignore':
            ignoreAccess = options.ignoreAccess;
            break;
          default:
            ignoreAccess = 'deny';
        }
      }

      return ignoreAccess;
    }
  });

  // max-age
  util.defineProperty(this, 'maxAge', {
    enumerable: true,
    get: function (){
      if (!maxAge) {
        maxAge = util.isType(options.maxAge, 'string')
          ? (ms(options.maxAge) || 0) / 1000
          : Number(options.maxAge);

        maxAge = maxAge >= 0
          ? Math.min(maxAge, MAXMAXAGE)
          : MAXMAXAGE;

        maxAge = Math.floor(maxAge);
      }

      return maxAge;
    }
  });

  // last-modified
  util.defineProperty(this, 'lastModified', {
    enumerable: true,
    get: function (){
      if (!lastModified) {
        lastModified = options.lastModified !== undefined
          ? Boolean(options.lastModified)
          : true;
      }

      return lastModified;
    }
  });

  // stream
  util.defineProperty(this, 'stream', {
    value: through(),
    writable: true,
    enumerable: false
  });

  // stream
  util.defineProperty(this, '_stream', {
    enumerable: false,
    set: function (value){
      stream = value;
    },
    get: function (){
      return stream || this.stream;
    }
  });

  this.views = options.views || {};

  this.status(200);
}

FileSend.prototype = Object.create(EventEmitter.prototype, {
  constructor: { value: FileSend }
});

/**
 * check if this is a conditional GET request.
 * @return {Boolean}
 * @api private
 */
FileSend.prototype.isConditionalGET = function (){
  return this.request.headers['if-none-match']
    || this.request.headers['if-modified-since'];
};

/**
 * check if the request is cacheable, aka
 * responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}).
 * @param response
 * @return {Boolean}
 * @api private
 */
FileSend.prototype.isCachable = function (response){
  var statusCode = response.statusCode;

  return statusCode === 304
    || (statusCode >= 200 && statusCode < 300);
};

/**
 * Check if the cache is fresh.
 * @param response
 * @return {Boolean}
 * @api private
 */
FileSend.prototype.isFresh = function (response){
  return fresh(this.request.headers, {
    'etag': response.getHeader('etag'),
    'last-modified': response.getHeader('last-modified')
  });
};

/**
 * Check if the range is fresh.
 * @param response
 * @return {Boolean}
 * @api private
 */
FileSend.prototype.isRangeFresh = function (response){
  var ifRange = this.request.headers['if-range'];

  if (!ifRange) {
    return true;
  }

  return ~ifRange.indexOf('"')
    ? ~ifRange.indexOf(response.getHeader('etag'))
    : Date.parse(response.getHeader('last-modified')) <= Date.parse(ifRange);
};

/**
 * set response status
 * @param statusCode
 * @api private
 */
FileSend.prototype.status = function (statusCode){
  this.statusCode = statusCode;
  this.statusMessage = http.STATUS_CODES[statusCode];
};

/**
 * error
 * @api private
 */
FileSend.prototype.error = function (status, message){
  switch (arguments.length) {
    case 0:
      break;
    case 1:
      this.status(status);
      break;
    case 2:
      this.statusCode = status;
      this.statusMessage = message;
      break;
  }

  status = this.statusCode;
  message = this.statusMessage;

  var error = new Error(message);

  error.statusCode = status;

  // emit if listeners instead of responding
  if (listenerCount(this, 'error') > 0) {
    return this.emit('error', error);
  }

  this.stream.end(message);
};

/**
 * dir
 * @api private
 */
FileSend.prototype.dir = function (){
  // emit if listeners instead of responding
  if (listenerCount(this, 'error') > 0) {
    return this.emit('error', error);
  }

  this.stream.end();
};

FileSend.prototype.redirect = function (){
  this.status(301);

  var location = encodeURI(this.path.slice(-1) === '/' ? this.path : this.path + '/');
  var message = 'Redirecting to ' + location + '';

  this.headers['Content-Type'] = 'text/html; charset=UTF-8';
  this.headers['Content-Length'] = Buffer.byteLength(message);
  this.headers['X-Content-Type-Options'] = 'nosniff';
  this.headers['Location'] = location;

  this.stream.end();
};

FileSend.prototype.read = function (response){
  // path error
  if (this.path === -1) {
    return this.error(400);
  }

  // conditional GET support
  if (this.isConditionalGET() && this.isCachable(response) && this.isFresh(response)) {
    this.status(304);

    return this.stream.end();
  }

  this.stream.end();
};

FileSend.prototype.pipe = function (response){
  if (response instanceof http.OutgoingMessage) {
    this.read(response);
    response.writeHead(this.statusCode, this.statusMessage, this.headers);
  }

  this._stream = this._stream.pipe(response);

  return this;
};

http.createServer(function (request, response){
  var send = new FileSend(request, {
    ignore: ['*.js']
  });

  // console.log('url:', send.url);
  // console.log('path:', send.path);
  // console.log('root:', send.root);
  // console.log('etag:', send.etag);
  // console.log('ignore:', send.ignore);
  // console.log('ignore-access:', send.ignoreAccess);
  // console.log('max-age:', send.maxAge);
  // console.log('last-modified:', send.lastModified);

  send.pipe(response);
}).listen(9091, '127.0.0.1');
<commit_msg>update files<commit_after>/*!
 * index
 * Date: 2016/6/21
 * https://github.com/Nuintun/file-send
 *
 * This is licensed under the MIT License (MIT).
 * For details, see: https://github.com/Nuintun/file-send/blob/master/LICENSE
 */

'use strict';

// external lib
var ms = require('ms');
var path = require('path');
var http = require('http');
var fresh = require('fresh');
var util = require('./lib/util');
var parseUrl = require('url').parse;
var micromatch = require('micromatch');
var through = require('./lib/through');
var escapeHtml = require('escape-html');
var EventEmitter = require('events').EventEmitter;

// variable declaration
var CWD = process.cwd(); // current working directory
var MAXMAXAGE = 60 * 60 * 24 * 365; // the max max-age set

var listenerCount = EventEmitter.listenerCount
  || function (emitter, type){ return emitter.listeners(type).length; };

function FileSend(request, options){
  if (!(this instanceof FileSend)) {
    return new FileSend(request, options);
  }

  if (!(request instanceof http.IncomingMessage)) {
    throw new TypeError('The first argument must be a http request.');
  }

  this.headers = {};
  this.request = request;
  this.method = this.request.method;

  options = options || {};

  var url, path, root, etag, ignore, ignoreAccess, maxAge, lastModified, stream;

  // url
  util.defineProperty(this, 'url', {
    enumerable: true,
    get: function (){
      if (!url) {
        url = util.decodeURI(request.url);
        url = -1 ? url : util.posixPath(url);
      }

      return url;
    }
  });

  // path
  util.defineProperty(this, 'path', {
    enumerable: true,
    get: function (){
      if (!path) {
        path = this.url === -1
          ? url
          : parseUrl(this.url).pathname;
      }

      return path;
    }
  });

  // root
  util.defineProperty(this, 'root', {
    enumerable: true,
    get: function (){
      if (!root) {
        root = util.isType(options.root, 'string')
          ? path.resolve(options.root)
          : CWD;

        root = util.posixPath(root + path.sep);
      }

      return root;
    }
  });

  // etag
  util.defineProperty(this, 'etag', {
    enumerable: true,
    get: function (){
      if (!etag) {
        etag = options.etag !== undefined
          ? Boolean(options.etag)
          : true;
      }

      return etag;
    }
  });

  // ignore
  util.defineProperty(this, 'ignore', {
    enumerable: true,
    get: function (){
      if (!ignore) {
        if (this.ignoreAccess !== 'allow') {
          ignore = Array.isArray(options.ignore)
            ? options.ignore
            : [options.ignore];

          ignore = ignore.filter(function (pattern){
            return util.isType(pattern, 'string')
              || util.isType(pattern, 'regexp')
              || util.isType(pattern, 'function');
          });
        } else {
          ignore = [];
        }
      }

      return ignore;
    }
  });

  // ignore-access
  util.defineProperty(this, 'ignoreAccess', {
    enumerable: true,
    get: function (){
      if (!ignoreAccess) {
        switch (options.ignoreAccess) {
          case 'allow':
          case 'deny':
          case 'ignore':
            ignoreAccess = options.ignoreAccess;
            break;
          default:
            ignoreAccess = 'deny';
        }
      }

      return ignoreAccess;
    }
  });

  // max-age
  util.defineProperty(this, 'maxAge', {
    enumerable: true,
    get: function (){
      if (!maxAge) {
        maxAge = util.isType(options.maxAge, 'string')
          ? (ms(options.maxAge) || 0) / 1000
          : Number(options.maxAge);

        maxAge = maxAge >= 0
          ? Math.min(maxAge, MAXMAXAGE)
          : MAXMAXAGE;

        maxAge = Math.floor(maxAge);
      }

      return maxAge;
    }
  });

  // last-modified
  util.defineProperty(this, 'lastModified', {
    enumerable: true,
    get: function (){
      if (!lastModified) {
        lastModified = options.lastModified !== undefined
          ? Boolean(options.lastModified)
          : true;
      }

      return lastModified;
    }
  });

  // stream
  util.defineProperty(this, 'stream', {
    value: through(),
    writable: true,
    enumerable: false
  });

  // stream
  util.defineProperty(this, '_stream', {
    enumerable: false,
    set: function (value){
      stream = value;
    },
    get: function (){
      return stream || this.stream;
    }
  });

  this.views = options.views || {};

  this.status(200);
}

FileSend.prototype = Object.create(EventEmitter.prototype, {
  constructor: { value: FileSend }
});

/**
 * check if this is a conditional GET request.
 * @return {Boolean}
 * @api private
 */
FileSend.prototype.isConditionalGET = function (){
  return this.request.headers['if-none-match']
    || this.request.headers['if-modified-since'];
};

/**
 * check if the request is cacheable, aka
 * responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}).
 * @param response
 * @return {Boolean}
 * @api private
 */
FileSend.prototype.isCachable = function (response){
  var statusCode = response.statusCode;

  return statusCode === 304
    || (statusCode >= 200 && statusCode < 300);
};

/**
 * Check if the cache is fresh.
 * @param response
 * @return {Boolean}
 * @api private
 */
FileSend.prototype.isFresh = function (response){
  return fresh(this.request.headers, {
    'etag': response.getHeader('etag'),
    'last-modified': response.getHeader('last-modified')
  });
};

/**
 * Check if the range is fresh.
 * @param response
 * @return {Boolean}
 * @api private
 */
FileSend.prototype.isRangeFresh = function (response){
  var ifRange = this.request.headers['if-range'];

  if (!ifRange) {
    return true;
  }

  return ~ifRange.indexOf('"')
    ? ~ifRange.indexOf(response.getHeader('etag'))
    : Date.parse(response.getHeader('last-modified')) <= Date.parse(ifRange);
};

/**
 * set response status
 * @param statusCode
 * @api private
 */
FileSend.prototype.status = function (statusCode){
  this.statusCode = statusCode;
  this.statusMessage = http.STATUS_CODES[statusCode];
};

/**
 * error
 * @api private
 */
FileSend.prototype.error = function (status, message){
  switch (arguments.length) {
    case 0:
      break;
    case 1:
      this.status(status);
      break;
    case 2:
      this.statusCode = status;
      this.statusMessage = message;
      break;
  }

  status = this.statusCode;
  message = this.statusMessage;

  var error = new Error(message);

  error.statusCode = status;

  // emit if listeners instead of responding
  if (listenerCount(this, 'error') > 0) {
    return this.emit('error', error);
  }

  this.stream.end(message);
};

/**
 * dir
 * @api private
 */
FileSend.prototype.dir = function (){
  // if have event directory listener, use user define
  if (listenerCount(this, 'dir') > 0) {
    // emit event directory
    return this.emit('dir');
  }

  this.stream.end();
};

FileSend.prototype.redirect = function (){
  this.status(301);

  var location = encodeURI(this.path.slice(-1) === '/' ? this.path : this.path + '/');
  var message = 'Redirecting to ' + location + '';

  this.headers['Content-Type'] = 'text/html; charset=UTF-8';
  this.headers['Content-Length'] = Buffer.byteLength(message);
  this.headers['X-Content-Type-Options'] = 'nosniff';
  this.headers['Location'] = location;

  this.stream.end();
};

FileSend.prototype.read = function (response){
  // path error
  if (this.path === -1) {
    return this.error(400);
  }

  // conditional GET support
  if (this.isConditionalGET() && this.isCachable(response) && this.isFresh(response)) {
    this.status(304);

    return this.stream.end();
  }

  this.stream.end();
};

FileSend.prototype.pipe = function (response){
  if (response instanceof http.OutgoingMessage) {
    this.read(response);
    response.writeHead(this.statusCode, this.statusMessage, this.headers);
  }

  this._stream = this._stream.pipe(response);

  return this;
};

http.createServer(function (request, response){
  var send = new FileSend(request, {
    ignore: ['*.js']
  });

  // console.log('url:', send.url);
  // console.log('path:', send.path);
  // console.log('root:', send.root);
  // console.log('etag:', send.etag);
  // console.log('ignore:', send.ignore);
  // console.log('ignore-access:', send.ignoreAccess);
  // console.log('max-age:', send.maxAge);
  // console.log('last-modified:', send.lastModified);

  send.pipe(response);
}).listen(9091, '127.0.0.1');
<|endoftext|>


<commit_before>require('!style!css!sass!./Button.scss')
import React, { Component } from 'react'
import PropTypes from 'prop-types'

class Button extends Component{
  constructor(props) {
    super(props)
    this.state = {
      label: this.props.open ? 'Fermer' : this.props.labelClosed
    }
  }

  componentWillReceiveProps() {
    this.setState({
      label: this.props.open ? 'Fermer' : this.props.labelClosed
    })
  }

  handleClick(e) {
    const {config} = this.props
    let text = this.props.open ? this.props.labelOpened : e.target.innerText
    config.tts.speak(text)
  }

  handleDoubleClick() {
    this.props.toggleMenu(this.props.id)
  }

  render() {
    return (
      <button id={this.props.id} type="button" className="button fill black" onClick={e => this.handleClick(e)} onDoubleClick={() => this.handleDoubleClick()}>
        {this.state.label}
      </button>
    )
  }
}

Button.contextTypes = {
  router: PropTypes.object.isRequired
}

export default Button<commit_msg>Refacto Button with cancel tts #74<commit_after>require('!style!css!sass!./Button.scss')
import React, { Component } from 'react'
import PropTypes from 'prop-types'

class Button extends Component{
  constructor(props) {
    super(props)
    this.state = {
      label: this.props.open ? 'Fermer' : this.props.labelClosed
    }
  }

  componentWillReceiveProps() {
    this.setState({
      label: this.props.open ? 'Fermer' : this.props.labelClosed
    })
  }

  handleClick(e) {
    const {config} = this.props
    let text = this.props.open ? this.props.labelOpened : e.target.innerText
    config.tts.speak(text)
  }

  handleDoubleClick() {
    const {config} = this.props
    config.tts.cancel()
    this.props.toggleMenu(this.props.id)
  }

  render() {
    return (
      <button
        id={this.props.id}
        type="button"
        className="button fill black"
        onClick={e => this.handleClick(e)}
        onDoubleClick={() => this.handleDoubleClick()}
      >
        {this.state.label}
      </button>
    )
  }
}

Button.contextTypes = {
  router: PropTypes.object.isRequired
}

export default Button<|endoftext|>


<commit_before>var start = Date.now()
var defer = require('pull-defer')
var observ = require('observable')
var pull = require('pull-stream')
var Scroll = require('pull-scroll')
var h = require('hyperscript')
var ssbc = require('ssb-client')
var markdown = require('ssb-markdown')
var Cat = require('pull-cat')
var mentions = require('ssb-mentions')

var fs = require('fs')
var path = require('path')

var API = require('./api')

var suggest = require('suggest-box')

var Columns = require('column-deck')
var Stack = require('column-deck/stack')

var moment = require('moment')
var jade = require('jade')

function px (n) { return n+'px' }

// With this data, render the given template at path
function Jade (data, template_path){
  var rendered_page = jade.renderFile(template_path, data);
  var new_page_element = document.createElement('span')

  new_page_element.innerHTML = rendered_page;

  return new_page_element;
}

var dock = Columns({width: 600, margin: 20})

document.body.style.margin = px(0)
document.body.style.padding = px(0)

document.body.appendChild(h('style', '.selected { color: red };'))

document.body.appendChild(h('style',
  fs.readFileSync(path.join(__dirname, 'style.css'), 'utf8')
))
document.body.appendChild(h('style',
  fs.readFileSync(path.join(__dirname, 'bootstrap.min.css'), 'utf8')
))

var lightbox = require('./lightbox')()
document.body.appendChild(lightbox)

var status = require('./status')()
document.body.appendChild(status)

//document.body.appendChild(dock)

function click(name, action) {
  return h('a', {href: '#', onclick: action}, name)
}

function maxKey (obj) {
  var m
  for (var k in obj)
    m = (m == null ? k : obj[m] < obj[k] ? k : m)
  return m
}

function once (cont) {
  var ended = false
  return function (abort, cb) {
    if(abort) return cb(abort)
    else if (ended) return cb(ended)
    else
      cont(function (err, data) {
        if(err) return cb(ended = err)
        ended = true
        cb(null, data)
      })
  }
}

function first(stream, cb) {
  pull(stream, pull.find(cb))
}

function name (id) {
  var n = h('span', id.substring(0, 10))
  //choose the most popular name for this person.
  //for anything like this you'll see I have used sbot.links2
  //which is the ssb-links plugin. as you'll see the query interface
  //is pretty powerful!
  //TODO: "most popular" name is easily gameable.
  //must come up with something better than this.
  first(
    sbot.links2.read({query: [
      {$filter: {rel: ['mentions', {$prefix: '@'}], dest: id}},
      {$reduce: {
        $group: [['rel', 1]],
        $count: true
      }}
    ]}),
    function (err, names) {
      if(err) throw err
      n.textContent = maxKey(names) || id.substring(0, 10)
    })

  return n
}

//show how many people have voted, replied, or mentioned this message.
function stats (id) {
  var span = h('span')
  first(
    sbot.links2.read({query: [
      {$filter: {dest: id}},
      {$reduce: {$group: [['rel', 0]], $collect: 'source'}}
    ]}),
    function (err, feedback) {

      if(feedback.root)
        span.appendChild(h('div', 'replies:', feedback.root.map(name)))
      if(feedback.vote)
        span.appendChild(h('div', 'yup:', feedback.vote.map(name)))
    }
  )
  return span
}

function feedback (id) {
  var f = h('span')

  first(
    sbot.links2.read({query: [
      {$filter: {dest: id}},
      {$reduce: {$group: [['rel', 0]], $count: true}}
    ]}), function (err, feedback) {
        if(err || !feedback) return
        var s = []
        if(feedback.root) s.push('R:'+feedback.root)
        //this ignores that votes can be positive or negative.
        //they are nearly always positive.
        if(feedback.vote) s.push('Y:'+feedback.vote)
        if(feedback.mention) s.push('M:'+feedback.mention)
        f.textContent = s.join(' ')
    })

  status.hover(f, function () {
    return stats(id)
  })

  return f
}

function toggle(off, on, change) {
  var state = false
  var a = h('a', {href: '#', onclick: function () {
    state = !state
    a.innerText = state ? on : off
    change(state)
  }}, off)
  change(state)
  return a
}

var streams = {
  all: function () {
    return API(sbot).curations()
  },
  user: function (id) {
    return sbot.createUserStream({ id: id, reverse: true })
  },
  thread: function (root) {
    //in this case, it's inconvienent that panel only takes
    //a stream. maybe it would be better to accept an array?
    var source = defer.source()
    pull(
      Cat([
        once(function (cb) {
          sbot.get(root, function (err, value) {
            cb(err, {key: root, value: value})
          })
        }),
        sbot.links({rel: 'root', dest: root, values: true, keys: true})
      ]),
      pull.collect(function (err, thread) {
        thread.sort(function (a, b) {
          //THIS IS WRONG AND HAS KNOWN BUGS!!!
          //TODO: sort by cryptographic causal ordering.
          return a.value.timestamp - b.value.timestamp
        })
        console.log(thread)
        source.resolve(pull.values(thread))
      })
    )

    return source
  }
}

function render (data) {
  if(!data.value) throw new Error('data missing value property')
  // console.log(data);

  return Jade(data, "view/post.jade")

  // return h('span', h('div.post',
  //   h('div.title',
  //     click(
  //       name(data.value.author),
  //       function () {
  //         createPanel(streams.user(data.value.author))
  //       }
  //     ),
  //     ' ',
  //     h('label', data.value.content.type || 'encrypted'),
  //     ' ',
  //     click(
  //       moment(data.value.timestamp).fromNow(),
  //       function () {
  //         createPanel(streams.thread(data.value.content.root || data.key))
  //       }
  //     )
  //   ),
  //   h('div', {
  //       style: {width: px(450), overflow: 'hidden'}
  //     },
  //     data.value.content.text ? (function () {
  //       var text = h('div')
  //       text.innerHTML = markdown.block(data.value.content.text, data.value.content.mentions)
  //       return text
  //     })() : h('pre', JSON.stringify(data.value.content))
  //   ),
  //   feedback(data.key)
  // ), h('hr'))
}

//create a panel (column) from a stream of messages.
//a stream has several advantages, for example,
//messages that don't need to be displayed yet won't
//be rendered, so it's gentler on the database.
//
//it's probably not ideal to just treat everything
//as a scolling column, for example, on a user feed
//we should show some "profile" information at the top.
//
//and for a thread, we want a composer to appear at the _bottom_
//(after you have read the whole thread)
//
//but the best thing about just making it a stream,
//is that I want to make a column be a rendering of any database query.
//(TODO, expand on the patterns in ssb-links)

function createPanel (el, stream) {
  var scroll = h('div', {
    style: {
      width: px(500),
      height: px(500), //MAGIC.
      margin: px(0),
      padding: px(0),
      overflow: 'scroll',
    }
  })

  el.innerHTML = ''

  var stack = Stack()
    .addFixed(h('h3', 'feed', {style: {background: 'grey'}},
      //UGLY: it's really ugly that I put the whole composer
      //just inline like this. this should really be refactored
      //out completely. maybe into it's own repo?
      //I don't have support for thread replies yet.
      //those would need to be more coupled to the messages in the
      //thread because they would need to get the most recent message
      //for the branch link.
      click('post', function () {
        var title = h('label', "title", h('input', {type: 'text'}))
        var summary = h('label', "summary", h('textarea', {rows: 20, cols: 80}))
        var one_liner = h('label', "one liner", h('textarea', {rows: 2, cols: 80}))
        var tags = h('label', "tags", h('input', {type: 'text'}))
        var prev = h('span')

        var form = h('form')

        form.appendChild(title)
        form.appendChild(summary)
        form.appendChild(one_liner)
        form.appendChild(tags)

        var tog
        lightbox.show(h('span',
          tog = h('div', form),
          h('br'),
          click('cancel', lightbox.close),
          ' ',
          toggle('preview', 'edit', function (state) {
            tog.innerHTML = ''
            if(!state){
              tog.appendChild(form)
            }
            else {
              prev.innerHTML = markdown.block(summary.value)
              tog.appendChild(prev)
            }
            lightbox.center()
          }),

          click('publish', function () {
            var content = {
              type: 'curation',
              summary: summary.value,
              mentions: mentions(summary.value),
              one_liner: one_liner.value,
              tags: tags.value.split()
            }
            sbot.publish(content, function (err, msg) {
              alert('published: '+ msg.key || JSON.stringify(msg))
              lightbox.close()
            })
          })
        ))

        suggest(summary, function (word, cb) {
          if(!/^[@%&!]/.test(word[0])) return cb()
          if(word.length < 2) return cb()

          var sigil = word[0]
          var embed = ((sigil === '!') ? '!' : '')
          if(embed) sigil = '&'
          if(word[0] !== '@') word = word.substring(1)

          first(
            sbot.links2.read({query: [
              {$filter: {rel: ['mentions', {$prefix: word}], dest: {$prefix: sigil}}},
              {$reduce: {$group: [['rel', 1], 'dest'], $count: true}}
            ]}),
            function (err, names) {
              var ary = []
              for(var name in names)
                for(var id in names[name])
                  ary.push({name: name, id: id, count: names[name][id]})

              ary = ary
              .filter(function (e) {
                if(!embed) return true
                return /\.(gif|jpg|png|svg)$/i.test(e.name)
              }).sort(function (a, b) {
                return b.count - a.count
              }).map(function (e) {
                return {
                  title: e.name + ': ' + e.id.substring(0,10)+' ('+e.count+')',
                  value: embed+'['+e.name+']('+e.id+')'
                }
              })

              cb(null, ary)
            }
          )
        })
      }),
      ' ',
      click('close', function () {
        dock.remove(stack)
      })
    ))
    .addFitted(scroll)

    el.appendChild(stack)

//  dock.add(stack)

  pull(
    stream,
    pull.filter(function (e) {
      if(!e.sync) return true
    }),
    Scroll(scroll, render, false, false)
  )

}

ssbc(function (err, _sbot) {
  if(err) {
    document.body.appendChild(
      ERR = h('pre', {style: {
        position: 'fixed', left: px(20), top: px(20)
      }}, err.stack)
    )
    return
  }
  console.log('connected...', Date.now() - start)
  sbot = _sbot
  var el = Jade(null, 'view/layout.jade')
  document.body.appendChild(el)
  var content = el.querySelector('#content')
  console.log()
  createPanel(content, streams.all())
//  createPanel(streams.all())
})
<commit_msg>labels for new content form<commit_after>var start = Date.now()
var defer = require('pull-defer')
var observ = require('observable')
var pull = require('pull-stream')
var Scroll = require('pull-scroll')
var h = require('hyperscript')
var ssbc = require('ssb-client')
var markdown = require('ssb-markdown')
var Cat = require('pull-cat')
var mentions = require('ssb-mentions')

var fs = require('fs')
var path = require('path')

var API = require('./api')

var suggest = require('suggest-box')

var Columns = require('column-deck')
var Stack = require('column-deck/stack')

var moment = require('moment')
var jade = require('jade')

function px (n) { return n+'px' }

// With this data, render the given template at path
function Jade (data, template_path){
  var rendered_page = jade.renderFile(template_path, data);
  var new_page_element = document.createElement('span')

  new_page_element.innerHTML = rendered_page;

  return new_page_element;
}

var dock = Columns({width: 600, margin: 20})

document.body.style.margin = px(0)
document.body.style.padding = px(0)

document.body.appendChild(h('style', '.selected { color: red };'))

document.body.appendChild(h('style',
  fs.readFileSync(path.join(__dirname, 'style.css'), 'utf8')
))
document.body.appendChild(h('style',
  fs.readFileSync(path.join(__dirname, 'bootstrap.min.css'), 'utf8')
))

var lightbox = require('./lightbox')()
document.body.appendChild(lightbox)

var status = require('./status')()
document.body.appendChild(status)

//document.body.appendChild(dock)

function click(name, action) {
  return h('a', {href: '#', onclick: action}, name)
}

function maxKey (obj) {
  var m
  for (var k in obj)
    m = (m == null ? k : obj[m] < obj[k] ? k : m)
  return m
}

function once (cont) {
  var ended = false
  return function (abort, cb) {
    if(abort) return cb(abort)
    else if (ended) return cb(ended)
    else
      cont(function (err, data) {
        if(err) return cb(ended = err)
        ended = true
        cb(null, data)
      })
  }
}

function first(stream, cb) {
  pull(stream, pull.find(cb))
}

function name (id) {
  var n = h('span', id.substring(0, 10))
  //choose the most popular name for this person.
  //for anything like this you'll see I have used sbot.links2
  //which is the ssb-links plugin. as you'll see the query interface
  //is pretty powerful!
  //TODO: "most popular" name is easily gameable.
  //must come up with something better than this.
  first(
    sbot.links2.read({query: [
      {$filter: {rel: ['mentions', {$prefix: '@'}], dest: id}},
      {$reduce: {
        $group: [['rel', 1]],
        $count: true
      }}
    ]}),
    function (err, names) {
      if(err) throw err
      n.textContent = maxKey(names) || id.substring(0, 10)
    })

  return n
}

//show how many people have voted, replied, or mentioned this message.
function stats (id) {
  var span = h('span')
  first(
    sbot.links2.read({query: [
      {$filter: {dest: id}},
      {$reduce: {$group: [['rel', 0]], $collect: 'source'}}
    ]}),
    function (err, feedback) {

      if(feedback.root)
        span.appendChild(h('div', 'replies:', feedback.root.map(name)))
      if(feedback.vote)
        span.appendChild(h('div', 'yup:', feedback.vote.map(name)))
    }
  )
  return span
}

function feedback (id) {
  var f = h('span')

  first(
    sbot.links2.read({query: [
      {$filter: {dest: id}},
      {$reduce: {$group: [['rel', 0]], $count: true}}
    ]}), function (err, feedback) {
        if(err || !feedback) return
        var s = []
        if(feedback.root) s.push('R:'+feedback.root)
        //this ignores that votes can be positive or negative.
        //they are nearly always positive.
        if(feedback.vote) s.push('Y:'+feedback.vote)
        if(feedback.mention) s.push('M:'+feedback.mention)
        f.textContent = s.join(' ')
    })

  status.hover(f, function () {
    return stats(id)
  })

  return f
}

function toggle(off, on, change) {
  var state = false
  var a = h('a', {href: '#', onclick: function () {
    state = !state
    a.innerText = state ? on : off
    change(state)
  }}, off)
  change(state)
  return a
}

var streams = {
  all: function () {
    return API(sbot).curations()
  },
  user: function (id) {
    return sbot.createUserStream({ id: id, reverse: true })
  },
  thread: function (root) {
    //in this case, it's inconvienent that panel only takes
    //a stream. maybe it would be better to accept an array?
    var source = defer.source()
    pull(
      Cat([
        once(function (cb) {
          sbot.get(root, function (err, value) {
            cb(err, {key: root, value: value})
          })
        }),
        sbot.links({rel: 'root', dest: root, values: true, keys: true})
      ]),
      pull.collect(function (err, thread) {
        thread.sort(function (a, b) {
          //THIS IS WRONG AND HAS KNOWN BUGS!!!
          //TODO: sort by cryptographic causal ordering.
          return a.value.timestamp - b.value.timestamp
        })
        console.log(thread)
        source.resolve(pull.values(thread))
      })
    )

    return source
  }
}

function render (data) {
  if(!data.value) throw new Error('data missing value property')
  // console.log(data);

  return Jade(data, "view/post.jade")

  // return h('span', h('div.post',
  //   h('div.title',
  //     click(
  //       name(data.value.author),
  //       function () {
  //         createPanel(streams.user(data.value.author))
  //       }
  //     ),
  //     ' ',
  //     h('label', data.value.content.type || 'encrypted'),
  //     ' ',
  //     click(
  //       moment(data.value.timestamp).fromNow(),
  //       function () {
  //         createPanel(streams.thread(data.value.content.root || data.key))
  //       }
  //     )
  //   ),
  //   h('div', {
  //       style: {width: px(450), overflow: 'hidden'}
  //     },
  //     data.value.content.text ? (function () {
  //       var text = h('div')
  //       text.innerHTML = markdown.block(data.value.content.text, data.value.content.mentions)
  //       return text
  //     })() : h('pre', JSON.stringify(data.value.content))
  //   ),
  //   feedback(data.key)
  // ), h('hr'))
}

//create a panel (column) from a stream of messages.
//a stream has several advantages, for example,
//messages that don't need to be displayed yet won't
//be rendered, so it's gentler on the database.
//
//it's probably not ideal to just treat everything
//as a scolling column, for example, on a user feed
//we should show some "profile" information at the top.
//
//and for a thread, we want a composer to appear at the _bottom_
//(after you have read the whole thread)
//
//but the best thing about just making it a stream,
//is that I want to make a column be a rendering of any database query.
//(TODO, expand on the patterns in ssb-links)

function createPanel (el, stream) {
  var scroll = h('div', {
    style: {
      width: px(500),
      height: px(500), //MAGIC.
      margin: px(0),
      padding: px(0),
      overflow: 'scroll',
    }
  })

  el.innerHTML = ''

  var stack = Stack()
    .addFixed(h('h3', 'feed', {style: {background: 'grey'}},
      //UGLY: it's really ugly that I put the whole composer
      //just inline like this. this should really be refactored
      //out completely. maybe into it's own repo?
      //I don't have support for thread replies yet.
      //those would need to be more coupled to the messages in the
      //thread because they would need to get the most recent message
      //for the branch link.
      click('post', function () {
        var title = h('input', {type: 'text'})
        var title_bundle = h('label', "title", title)

        var summary = h('textarea', {rows: 20, cols: 80})
        var summary_bundle = h('label', "summary", summary)

        var one_liner = h('textarea', {rows: 2, cols: 80})
        var one_liner_bundle = h('label', "one liner", one_liner)

        var tags = h('input', {type: 'text'})
        var tags_bundle = h('label', "tags", tags)

        var prev = h('span')

        var form = h('form')

        form.appendChild(title_bundle)
        form.appendChild(one_liner_bundle)
        form.appendChild(summary_bundle)
        form.appendChild(tags_bundle)

        var tog
        lightbox.show(h('span',
          tog = h('div', form),
          h('br'),
          click('cancel', lightbox.close),
          ' ',
          toggle('preview', 'edit', function (state) {
            tog.innerHTML = ''
            if(!state){
              tog.appendChild(form)
            }
            else {
              prev.innerHTML = markdown.block(summary.value)
              tog.appendChild(prev)
            }
            lightbox.center()
          }),

          click('publish', function () {
            var content = {
              type: 'curation',
              summary: summary.value,
              mentions: mentions(summary.value),
              one_liner: one_liner.value,
              tags: tags.value.split()
            }
            sbot.publish(content, function (err, msg) {
              alert('published: '+ msg.key || JSON.stringify(msg))
              lightbox.close()
            })
          })
        ))

        suggest(summary, function (word, cb) {
          if(!/^[@%&!]/.test(word[0])) return cb()
          if(word.length < 2) return cb()

          var sigil = word[0]
          var embed = ((sigil === '!') ? '!' : '')
          if(embed) sigil = '&'
          if(word[0] !== '@') word = word.substring(1)

          first(
            sbot.links2.read({query: [
              {$filter: {rel: ['mentions', {$prefix: word}], dest: {$prefix: sigil}}},
              {$reduce: {$group: [['rel', 1], 'dest'], $count: true}}
            ]}),
            function (err, names) {
              var ary = []
              for(var name in names)
                for(var id in names[name])
                  ary.push({name: name, id: id, count: names[name][id]})

              ary = ary
              .filter(function (e) {
                if(!embed) return true
                return /\.(gif|jpg|png|svg)$/i.test(e.name)
              }).sort(function (a, b) {
                return b.count - a.count
              }).map(function (e) {
                return {
                  title: e.name + ': ' + e.id.substring(0,10)+' ('+e.count+')',
                  value: embed+'['+e.name+']('+e.id+')'
                }
              })

              cb(null, ary)
            }
          )
        })
      }),
      ' ',
      click('close', function () {
        dock.remove(stack)
      })
    ))
    .addFitted(scroll)

    el.appendChild(stack)

//  dock.add(stack)

  pull(
    stream,
    pull.filter(function (e) {
      if(!e.sync) return true
    }),
    Scroll(scroll, render, false, false)
  )

}

ssbc(function (err, _sbot) {
  if(err) {
    document.body.appendChild(
      ERR = h('pre', {style: {
        position: 'fixed', left: px(20), top: px(20)
      }}, err.stack)
    )
    return
  }
  console.log('connected...', Date.now() - start)
  sbot = _sbot
  var el = Jade(null, 'view/layout.jade')
  document.body.appendChild(el)
  var content = el.querySelector('#content')
  console.log()
  createPanel(content, streams.all())
//  createPanel(streams.all())
})
<|endoftext|>