UNPKG

sybil-interface

Version:

A simple interface to interact with sybil websocket API

255 lines (247 loc) 9.08 kB
WebSocket = require("ws") EventEmitter = require("eventex").EventEmitter MessageCenter = require("message-center") States = require "logicoma" crypto = require "crypto" async = require "async" Errors = require("error-doc").create() .define("InterfaceError") .define("AlreadyExists") .generate() class SybilInterface extends EventEmitter constructor:(@port,@host = "localhost",option = {})-> super() @connectState = "close" @messageCenter = new MessageCenter() @mc = @messageCenter if option.useSSL @protocol = "wss:" else @protocol = "ws:" @username = option.username || "" @password = option.password || "" @authToken = crypto.createHash("md5").update(new Buffer(@username)).update(new Buffer(@password)).digest("hex") @path = option.path or "" connect:()-> if @connectState isnt "close" return false @connectState = "connecting" if @connection @connection.close() @connection = new WebSocket("#{@protocol}//#{@host}:#{@port}/#{@path}",{ headers:{ auth:@authToken } }) @connection.once "open",()=> @connectState = "connected" @messageCenter.setConnection(@connection) @emit "ready" @connection.once "error",(err)=> @close() @connection.once "close",()=> @close() close:()-> if @connectState is "close" return @connectState = "close" @messageCenter.unsetConnection() @connection.close() @connection.removeAllListeners() @connection = null; @emit "close" ready:(callback)-> if @connectState is "connected" callback() return if @connectState is "connecting" @once "ready",()-> callback() return if @connectState is "close" @connect() @once "ready",()-> callback() return throw new Error "Logic Error" # subscribe is for quick rss append # without authorization and captcha support # dirty but useful subscribe:(option = {},callback)=> detector = new SourceDetector(option.uri,this,{autoAccept:true,autoFail:true}) counter = 0 detector.on "candidates",(candidates)=> # wait until al candidates finish async.each candidates ,(candidate,done)=> if candidate.state in ["fail","done"] done() return candidate.on "finish",()->done() # ignore errors result in none "done" state # which will be filtered in thie dirty method candidate.on "error",()->done() ,()=> # done or failed with already exists result = candidates.filter (item)->item.state is "done" or item.data.source .map (item)->item.data.source callback null,result getFolders:(callback)-> @mc.invoke "getConfig","sourceFolderConfig",(err,data = {})=> folders = data.folders or [] callback err,folders setFolders:(folders,callback)-> @mc.invoke "saveConfig",{name:"sourceFolderConfig",data:{folders:folders}},(err)=> console.log "set source folder config",folders,err callback err class FolderBuilder constructor:(@folders)-> moveSourceToFolder:(source,folderName)-> # remove duplicate source for folder in @folders if folder.type isnt "folder" continue for child,index in folder.children if child.guid is source.guid folder.children[index] = null folder.children = folder.children.filter (item)->item for folder in @folders if folder.name is folderName targetFolder = folder break if not targetFolder targetFolder = {name:folderName,children:[],type:"folder"} @folders.push targetFolder targetFolder.children.push source toJson:()-> @folders class Candidate extends States constructor:(@candidate,@inf,@option)-> super() # @debug() @setState "waitResult" @data.candidate = @candidate atPanic:()-> @emit "error",@panicError atJudge:()-> if @option.autoAccept @setState "accept" else @waitFor "judge",(result)-> if result @setState "accept" else @setState "decline" atAccept:(sole)-> @inf.mc.invoke "acceptCandidate",@candidate.cid,(err)=> if @stale sole return if err @error new Errors.InterfaceError "fail to accept #{@candidate.cid}:#{@candidate.uri}" return @setState "waitResult" atWaitResult:(sole)-> clear = ()=> @inf.mc.stopListenBy this @inf.mc.listenBy this,"event/candidate/fail",(candidate)=> if candidate.cid is @candidate.cid clear() @data.candidate = candidate @setState "fail" @inf.mc.listenBy this,"event/candidate/subscribe",(candidate)=> if candidate.cid is @candidate.cid clear() @data.candidate = candidate @setState "waitSource" @inf.mc.listenBy this,"event/candidate/requireAuth",(candidate)=> if candidate.cid is @candidate.cid clear() @data.candidate = candidate @setState "requireAuth" @inf.mc.listenBy this,"event/candidate/requireCaptcha",(candidate)=> if candidate.cid is @candidate.cid clear() @data.candidate = candidate @setState "requireCaptcha" @inf.mc.listenBy this,"event/candidate/requireAccept",(candidate)=> if candidate.cid is @candidate.cid clear() @data.candidate = candidate @setState "judge" atWaitSource:()-> clear = ()=> @inf.mc.stopListenBy this @inf.mc.listenBy this,"event/source/duplicate",(source)=> if source.uri is @candidate.uri clear() @data.source = source @error new Errors.AlreadyExists("candidate already exists",{source:source}) @inf.mc.listenBy this,"event/source",(source)=> if source.uri is @candidate.uri clear() @data.source = source @setState "done" atFail:()=> @emit "fail" @emit "finish" atCancel:()=> if @option.autoFail @setState "fail" return @emit "cancel" @emit "finish" atDone:()=> @emit "subscribe",@data.candidate @emit "finish" atRequireAuth:(sole)=> if @option.autoFail @setState "fail" return @waitFor "auth",(username,secret)=> @inf.mc.invoke "authCandidate",{cid:@candidate.cid,username:username,secret:secret},(err,result)=> if @stale sole return if err @error new Errors.InterfaceError "fail to auth candidate" return @setState "waitResult" atRequireCaptcha:(sole)=> if @option.autoFail @setState "fail" return @waitFor "captcha",(result)=> @inf.mc.invoke "setCandidateCaptcha",{cid:@candidate.cid,captcha:result},(err)=> if @stale sole return if err @error new Errors.InterfaceError "fail to auth candidate" return @setState "waitResult" class SourceDetector extends States constructor:(@uri,@inf,@option = {})-> super() # @debug() @data.candidates = [] @setState "test" atPanic:()-> @emit "error",@panicError atTest:()-> @inf.mc.invoke "detectStream",@uri,(err,stream)=> if err @error new Errors.InterfaceError("fail to detect stream #{@uri}",via:err) return @data.stream = stream @setState "detect" atDetect:()-> @data.stream.on "data",(candidate)=> c = new Candidate(candidate,@inf,@option) @emit "candidate",c @data.candidates.push c @data.stream.on "end",()=> @data.stream.removeAllListeners() @setState "done" atDone:()-> @emit "candidates",@data.candidates @emit "done" module.exports = SybilInterface SybilInterface.FolderBuilder = FolderBuilder