sybil-interface
Version:
A simple interface to interact with sybil websocket API
255 lines (247 loc) • 9.08 kB
text/coffeescript
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:(, = "localhost",option = {})->
super()
= "close"
= new MessageCenter()
=
if option.useSSL
= "wss:"
else
= "ws:"
= option.username || ""
= option.password || ""
= crypto.createHash("md5").update(new Buffer()).update(new Buffer()).digest("hex")
= option.path or ""
connect:()->
if isnt "close"
return false
= "connecting"
if
.close()
= new WebSocket("#{@protocol}//#{@host}:#{@port}/#{@path}",{
headers:{
auth:
}
})
.once "open",()=>
= "connected"
.setConnection()
"ready"
.once "error",(err)=>
.once "close",()=>
close:()->
if is "close"
return
= "close"
.unsetConnection()
.close()
.removeAllListeners()
= null;
"close"
ready:(callback)->
if is "connected"
callback()
return
if is "connecting"
"ready",()->
callback()
return
if is "close"
"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)->
.invoke "getConfig","sourceFolderConfig",(err,data = {})=>
folders = data.folders or []
callback err,folders
setFolders:(folders,callback)->
.invoke "saveConfig",{name:"sourceFolderConfig",data:{folders:folders}},(err)=>
console.log "set source folder config",folders,err
callback err
class FolderBuilder
constructor:()->
moveSourceToFolder:(source,folderName)->
# remove duplicate source
for folder in
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
if folder.name is folderName
targetFolder = folder
break
if not targetFolder
targetFolder = {name:folderName,children:[],type:"folder"}
.push targetFolder
targetFolder.children.push source
toJson:()->
class Candidate extends States
constructor:(,,)->
super()
#
"waitResult"
.candidate =
atPanic:()->
"error",
atJudge:()->
if .autoAccept
"accept"
else
"judge",(result)->
if result
"accept"
else
"decline"
atAccept:(sole)->
.mc.invoke "acceptCandidate",.cid,(err)=>
if sole
return
if err
new Errors.InterfaceError "fail to accept #{@candidate.cid}:#{@candidate.uri}"
return
"waitResult"
atWaitResult:(sole)->
clear = ()=>
.mc.stopListenBy this
.mc.listenBy this,"event/candidate/fail",(candidate)=>
if candidate.cid is .cid
clear()
.candidate = candidate
"fail"
.mc.listenBy this,"event/candidate/subscribe",(candidate)=>
if candidate.cid is .cid
clear()
.candidate = candidate
"waitSource"
.mc.listenBy this,"event/candidate/requireAuth",(candidate)=>
if candidate.cid is .cid
clear()
.candidate = candidate
"requireAuth"
.mc.listenBy this,"event/candidate/requireCaptcha",(candidate)=>
if candidate.cid is .cid
clear()
.candidate = candidate
"requireCaptcha"
.mc.listenBy this,"event/candidate/requireAccept",(candidate)=>
if candidate.cid is .cid
clear()
.candidate = candidate
"judge"
atWaitSource:()->
clear = ()=>
.mc.stopListenBy this
.mc.listenBy this,"event/source/duplicate",(source)=>
if source.uri is .uri
clear()
.source = source
new Errors.AlreadyExists("candidate already exists",{source:source})
.mc.listenBy this,"event/source",(source)=>
if source.uri is .uri
clear()
.source = source
"done"
atFail:()=>
"fail"
"finish"
atCancel:()=>
if .autoFail
"fail"
return
"cancel"
"finish"
atDone:()=>
"subscribe",.candidate
"finish"
atRequireAuth:(sole)=>
if .autoFail
"fail"
return
"auth",(username,secret)=>
.mc.invoke "authCandidate",{cid:.cid,username:username,secret:secret},(err,result)=>
if sole
return
if err
new Errors.InterfaceError "fail to auth candidate"
return
"waitResult"
atRequireCaptcha:(sole)=>
if .autoFail
"fail"
return
"captcha",(result)=>
.mc.invoke "setCandidateCaptcha",{cid:.cid,captcha:result},(err)=>
if sole
return
if err
new Errors.InterfaceError "fail to auth candidate"
return
"waitResult"
class SourceDetector extends States
constructor:(,, = {})->
super()
#
.candidates = []
"test"
atPanic:()->
"error",
atTest:()->
.mc.invoke "detectStream",,(err,stream)=>
if err
new Errors.InterfaceError("fail to detect stream #{@uri}",via:err)
return
.stream = stream
"detect"
atDetect:()->
.stream.on "data",(candidate)=>
c = new Candidate(candidate,,)
"candidate",c
.candidates.push c
.stream.on "end",()=>
.stream.removeAllListeners()
"done"
atDone:()->
"candidates",.candidates
"done"
module.exports = SybilInterface
SybilInterface.FolderBuilder = FolderBuilder