123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193 |
- 'use strict';
- Object.defineProperty(exports, '__esModule', { value: true });
- function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
- var platformAdaptersNode = require('@leancloud/platform-adapters-node');
- var protobufLight = _interopDefault(require('protobufjs/dist/protobuf-light'));
- var EventEmitter = _interopDefault(require('eventemitter3'));
- var d = _interopDefault(require('debug'));
- var shuffle = _interopDefault(require('lodash/shuffle'));
- var values = _interopDefault(require('lodash/values'));
- var StateMachine = _interopDefault(require('javascript-state-machine'));
- var isPlainObject = _interopDefault(require('lodash/isPlainObject'));
- var promiseTimeout = require('promise-timeout');
- var uuid = _interopDefault(require('uuid/v4'));
- var base64Arraybuffer = require('base64-arraybuffer');
- var remove = _interopDefault(require('lodash/remove'));
- var isEmpty = _interopDefault(require('lodash/isEmpty'));
- var cloneDeep = _interopDefault(require('lodash/cloneDeep'));
- var find = _interopDefault(require('lodash/find'));
- var get = _interopDefault(require('lodash/get'));
- var messageCompiled = protobufLight.newBuilder({})['import']({
- package: 'push_server.messages2',
- syntax: 'proto2',
- options: {
- objc_class_prefix: 'AVIM'
- },
- messages: [{
- name: 'JsonObjectMessage',
- syntax: 'proto2',
- fields: [{
- rule: 'required',
- type: 'string',
- name: 'data',
- id: 1
- }]
- }, {
- name: 'UnreadTuple',
- syntax: 'proto2',
- fields: [{
- rule: 'required',
- type: 'string',
- name: 'cid',
- id: 1
- }, {
- rule: 'required',
- type: 'int32',
- name: 'unread',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 'mid',
- id: 3
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'timestamp',
- id: 4
- }, {
- rule: 'optional',
- type: 'string',
- name: 'from',
- id: 5
- }, {
- rule: 'optional',
- type: 'string',
- name: 'data',
- id: 6
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'patchTimestamp',
- id: 7
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'mentioned',
- id: 8
- }, {
- rule: 'optional',
- type: 'bytes',
- name: 'binaryMsg',
- id: 9
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'convType',
- id: 10
- }]
- }, {
- name: 'LogItem',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'from',
- id: 1
- }, {
- rule: 'optional',
- type: 'string',
- name: 'data',
- id: 2
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'timestamp',
- id: 3
- }, {
- rule: 'optional',
- type: 'string',
- name: 'msgId',
- id: 4
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'ackAt',
- id: 5
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'readAt',
- id: 6
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'patchTimestamp',
- id: 7
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'mentionAll',
- id: 8
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'mentionPids',
- id: 9
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'bin',
- id: 10
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'convType',
- id: 11
- }]
- }, {
- name: 'ConvMemberInfo',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'pid',
- id: 1
- }, {
- rule: 'optional',
- type: 'string',
- name: 'role',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 'infoId',
- id: 3
- }]
- }, {
- name: 'DataCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'repeated',
- type: 'string',
- name: 'ids',
- id: 1
- }, {
- rule: 'repeated',
- type: 'JsonObjectMessage',
- name: 'msg',
- id: 2
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'offline',
- id: 3
- }]
- }, {
- name: 'SessionCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'int64',
- name: 't',
- id: 1
- }, {
- rule: 'optional',
- type: 'string',
- name: 'n',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 's',
- id: 3
- }, {
- rule: 'optional',
- type: 'string',
- name: 'ua',
- id: 4
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'r',
- id: 5
- }, {
- rule: 'optional',
- type: 'string',
- name: 'tag',
- id: 6
- }, {
- rule: 'optional',
- type: 'string',
- name: 'deviceId',
- id: 7
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'sessionPeerIds',
- id: 8
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'onlineSessionPeerIds',
- id: 9
- }, {
- rule: 'optional',
- type: 'string',
- name: 'st',
- id: 10
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'stTtl',
- id: 11
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'code',
- id: 12
- }, {
- rule: 'optional',
- type: 'string',
- name: 'reason',
- id: 13
- }, {
- rule: 'optional',
- type: 'string',
- name: 'deviceToken',
- id: 14
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'sp',
- id: 15
- }, {
- rule: 'optional',
- type: 'string',
- name: 'detail',
- id: 16
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'lastUnreadNotifTime',
- id: 17
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'lastPatchTime',
- id: 18
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'configBitmap',
- id: 19
- }]
- }, {
- name: 'ErrorCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'required',
- type: 'int32',
- name: 'code',
- id: 1
- }, {
- rule: 'required',
- type: 'string',
- name: 'reason',
- id: 2
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'appCode',
- id: 3
- }, {
- rule: 'optional',
- type: 'string',
- name: 'detail',
- id: 4
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'pids',
- id: 5
- }, {
- rule: 'optional',
- type: 'string',
- name: 'appMsg',
- id: 6
- }]
- }, {
- name: 'DirectCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'msg',
- id: 1
- }, {
- rule: 'optional',
- type: 'string',
- name: 'uid',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 'fromPeerId',
- id: 3
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'timestamp',
- id: 4
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'offline',
- id: 5
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'hasMore',
- id: 6
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'toPeerIds',
- id: 7
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'r',
- id: 10
- }, {
- rule: 'optional',
- type: 'string',
- name: 'cid',
- id: 11
- }, {
- rule: 'optional',
- type: 'string',
- name: 'id',
- id: 12
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'transient',
- id: 13
- }, {
- rule: 'optional',
- type: 'string',
- name: 'dt',
- id: 14
- }, {
- rule: 'optional',
- type: 'string',
- name: 'roomId',
- id: 15
- }, {
- rule: 'optional',
- type: 'string',
- name: 'pushData',
- id: 16
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'will',
- id: 17
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'patchTimestamp',
- id: 18
- }, {
- rule: 'optional',
- type: 'bytes',
- name: 'binaryMsg',
- id: 19
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'mentionPids',
- id: 20
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'mentionAll',
- id: 21
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'convType',
- id: 22
- }]
- }, {
- name: 'AckCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'int32',
- name: 'code',
- id: 1
- }, {
- rule: 'optional',
- type: 'string',
- name: 'reason',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 'mid',
- id: 3
- }, {
- rule: 'optional',
- type: 'string',
- name: 'cid',
- id: 4
- }, {
- rule: 'optional',
- type: 'int64',
- name: 't',
- id: 5
- }, {
- rule: 'optional',
- type: 'string',
- name: 'uid',
- id: 6
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'fromts',
- id: 7
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'tots',
- id: 8
- }, {
- rule: 'optional',
- type: 'string',
- name: 'type',
- id: 9
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'ids',
- id: 10
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'appCode',
- id: 11
- }, {
- rule: 'optional',
- type: 'string',
- name: 'appMsg',
- id: 12
- }]
- }, {
- name: 'UnreadCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'repeated',
- type: 'UnreadTuple',
- name: 'convs',
- id: 1
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'notifTime',
- id: 2
- }]
- }, {
- name: 'ConvCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'repeated',
- type: 'string',
- name: 'm',
- id: 1
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'transient',
- id: 2
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'unique',
- id: 3
- }, {
- rule: 'optional',
- type: 'string',
- name: 'cid',
- id: 4
- }, {
- rule: 'optional',
- type: 'string',
- name: 'cdate',
- id: 5
- }, {
- rule: 'optional',
- type: 'string',
- name: 'initBy',
- id: 6
- }, {
- rule: 'optional',
- type: 'string',
- name: 'sort',
- id: 7
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'limit',
- id: 8
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'skip',
- id: 9
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'flag',
- id: 10
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'count',
- id: 11
- }, {
- rule: 'optional',
- type: 'string',
- name: 'udate',
- id: 12
- }, {
- rule: 'optional',
- type: 'int64',
- name: 't',
- id: 13
- }, {
- rule: 'optional',
- type: 'string',
- name: 'n',
- id: 14
- }, {
- rule: 'optional',
- type: 'string',
- name: 's',
- id: 15
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'statusSub',
- id: 16
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'statusPub',
- id: 17
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'statusTTL',
- id: 18
- }, {
- rule: 'optional',
- type: 'string',
- name: 'uniqueId',
- id: 19
- }, {
- rule: 'optional',
- type: 'string',
- name: 'targetClientId',
- id: 20
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'maxReadTimestamp',
- id: 21
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'maxAckTimestamp',
- id: 22
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'queryAllMembers',
- id: 23
- }, {
- rule: 'repeated',
- type: 'MaxReadTuple',
- name: 'maxReadTuples',
- id: 24
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'cids',
- id: 25
- }, {
- rule: 'optional',
- type: 'ConvMemberInfo',
- name: 'info',
- id: 26
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'tempConv',
- id: 27
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'tempConvTTL',
- id: 28
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'tempConvIds',
- id: 29
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'allowedPids',
- id: 30
- }, {
- rule: 'repeated',
- type: 'ErrorCommand',
- name: 'failedPids',
- id: 31
- }, {
- rule: 'optional',
- type: 'string',
- name: 'next',
- id: 40
- }, {
- rule: 'optional',
- type: 'JsonObjectMessage',
- name: 'results',
- id: 100
- }, {
- rule: 'optional',
- type: 'JsonObjectMessage',
- name: 'where',
- id: 101
- }, {
- rule: 'optional',
- type: 'JsonObjectMessage',
- name: 'attr',
- id: 103
- }, {
- rule: 'optional',
- type: 'JsonObjectMessage',
- name: 'attrModified',
- id: 104
- }]
- }, {
- name: 'RoomCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'roomId',
- id: 1
- }, {
- rule: 'optional',
- type: 'string',
- name: 's',
- id: 2
- }, {
- rule: 'optional',
- type: 'int64',
- name: 't',
- id: 3
- }, {
- rule: 'optional',
- type: 'string',
- name: 'n',
- id: 4
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'transient',
- id: 5
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'roomPeerIds',
- id: 6
- }, {
- rule: 'optional',
- type: 'string',
- name: 'byPeerId',
- id: 7
- }]
- }, {
- name: 'LogsCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'cid',
- id: 1
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'l',
- id: 2
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'limit',
- id: 3
- }, {
- rule: 'optional',
- type: 'int64',
- name: 't',
- id: 4
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'tt',
- id: 5
- }, {
- rule: 'optional',
- type: 'string',
- name: 'tmid',
- id: 6
- }, {
- rule: 'optional',
- type: 'string',
- name: 'mid',
- id: 7
- }, {
- rule: 'optional',
- type: 'string',
- name: 'checksum',
- id: 8
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'stored',
- id: 9
- }, {
- rule: 'optional',
- type: 'QueryDirection',
- name: 'direction',
- id: 10,
- options: {
- default: 'OLD'
- }
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'tIncluded',
- id: 11
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'ttIncluded',
- id: 12
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'lctype',
- id: 13
- }, {
- rule: 'repeated',
- type: 'LogItem',
- name: 'logs',
- id: 105
- }],
- enums: [{
- name: 'QueryDirection',
- syntax: 'proto2',
- values: [{
- name: 'OLD',
- id: 1
- }, {
- name: 'NEW',
- id: 2
- }]
- }]
- }, {
- name: 'RcpCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'id',
- id: 1
- }, {
- rule: 'optional',
- type: 'string',
- name: 'cid',
- id: 2
- }, {
- rule: 'optional',
- type: 'int64',
- name: 't',
- id: 3
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'read',
- id: 4
- }, {
- rule: 'optional',
- type: 'string',
- name: 'from',
- id: 5
- }]
- }, {
- name: 'ReadTuple',
- syntax: 'proto2',
- fields: [{
- rule: 'required',
- type: 'string',
- name: 'cid',
- id: 1
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'timestamp',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 'mid',
- id: 3
- }]
- }, {
- name: 'MaxReadTuple',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'pid',
- id: 1
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'maxAckTimestamp',
- id: 2
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'maxReadTimestamp',
- id: 3
- }]
- }, {
- name: 'ReadCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'cid',
- id: 1
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'cids',
- id: 2
- }, {
- rule: 'repeated',
- type: 'ReadTuple',
- name: 'convs',
- id: 3
- }]
- }, {
- name: 'PresenceCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'StatusType',
- name: 'status',
- id: 1
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'sessionPeerIds',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 'cid',
- id: 3
- }]
- }, {
- name: 'ReportCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'bool',
- name: 'initiative',
- id: 1
- }, {
- rule: 'optional',
- type: 'string',
- name: 'type',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 'data',
- id: 3
- }]
- }, {
- name: 'PatchItem',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'cid',
- id: 1
- }, {
- rule: 'optional',
- type: 'string',
- name: 'mid',
- id: 2
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'timestamp',
- id: 3
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'recall',
- id: 4
- }, {
- rule: 'optional',
- type: 'string',
- name: 'data',
- id: 5
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'patchTimestamp',
- id: 6
- }, {
- rule: 'optional',
- type: 'string',
- name: 'from',
- id: 7
- }, {
- rule: 'optional',
- type: 'bytes',
- name: 'binaryMsg',
- id: 8
- }, {
- rule: 'optional',
- type: 'bool',
- name: 'mentionAll',
- id: 9
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'mentionPids',
- id: 10
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'patchCode',
- id: 11
- }, {
- rule: 'optional',
- type: 'string',
- name: 'patchReason',
- id: 12
- }]
- }, {
- name: 'PatchCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'repeated',
- type: 'PatchItem',
- name: 'patches',
- id: 1
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'lastPatchTime',
- id: 2
- }]
- }, {
- name: 'PubsubCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'cid',
- id: 1
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'cids',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 'topic',
- id: 3
- }, {
- rule: 'optional',
- type: 'string',
- name: 'subtopic',
- id: 4
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'topics',
- id: 5
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'subtopics',
- id: 6
- }, {
- rule: 'optional',
- type: 'JsonObjectMessage',
- name: 'results',
- id: 7
- }]
- }, {
- name: 'BlacklistCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'string',
- name: 'srcCid',
- id: 1
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'toPids',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 'srcPid',
- id: 3
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'toCids',
- id: 4
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'limit',
- id: 5
- }, {
- rule: 'optional',
- type: 'string',
- name: 'next',
- id: 6
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'blockedPids',
- id: 8
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'blockedCids',
- id: 9
- }, {
- rule: 'repeated',
- type: 'string',
- name: 'allowedPids',
- id: 10
- }, {
- rule: 'repeated',
- type: 'ErrorCommand',
- name: 'failedPids',
- id: 11
- }, {
- rule: 'optional',
- type: 'int64',
- name: 't',
- id: 12
- }, {
- rule: 'optional',
- type: 'string',
- name: 'n',
- id: 13
- }, {
- rule: 'optional',
- type: 'string',
- name: 's',
- id: 14
- }]
- }, {
- name: 'GenericCommand',
- syntax: 'proto2',
- fields: [{
- rule: 'optional',
- type: 'CommandType',
- name: 'cmd',
- id: 1
- }, {
- rule: 'optional',
- type: 'OpType',
- name: 'op',
- id: 2
- }, {
- rule: 'optional',
- type: 'string',
- name: 'appId',
- id: 3
- }, {
- rule: 'optional',
- type: 'string',
- name: 'peerId',
- id: 4
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'i',
- id: 5
- }, {
- rule: 'optional',
- type: 'string',
- name: 'installationId',
- id: 6
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'priority',
- id: 7
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'service',
- id: 8
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'serverTs',
- id: 9
- }, {
- rule: 'optional',
- type: 'int64',
- name: 'clientTs',
- id: 10
- }, {
- rule: 'optional',
- type: 'int32',
- name: 'notificationType',
- id: 11
- }, {
- rule: 'optional',
- type: 'DataCommand',
- name: 'dataMessage',
- id: 101
- }, {
- rule: 'optional',
- type: 'SessionCommand',
- name: 'sessionMessage',
- id: 102
- }, {
- rule: 'optional',
- type: 'ErrorCommand',
- name: 'errorMessage',
- id: 103
- }, {
- rule: 'optional',
- type: 'DirectCommand',
- name: 'directMessage',
- id: 104
- }, {
- rule: 'optional',
- type: 'AckCommand',
- name: 'ackMessage',
- id: 105
- }, {
- rule: 'optional',
- type: 'UnreadCommand',
- name: 'unreadMessage',
- id: 106
- }, {
- rule: 'optional',
- type: 'ReadCommand',
- name: 'readMessage',
- id: 107
- }, {
- rule: 'optional',
- type: 'RcpCommand',
- name: 'rcpMessage',
- id: 108
- }, {
- rule: 'optional',
- type: 'LogsCommand',
- name: 'logsMessage',
- id: 109
- }, {
- rule: 'optional',
- type: 'ConvCommand',
- name: 'convMessage',
- id: 110
- }, {
- rule: 'optional',
- type: 'RoomCommand',
- name: 'roomMessage',
- id: 111
- }, {
- rule: 'optional',
- type: 'PresenceCommand',
- name: 'presenceMessage',
- id: 112
- }, {
- rule: 'optional',
- type: 'ReportCommand',
- name: 'reportMessage',
- id: 113
- }, {
- rule: 'optional',
- type: 'PatchCommand',
- name: 'patchMessage',
- id: 114
- }, {
- rule: 'optional',
- type: 'PubsubCommand',
- name: 'pubsubMessage',
- id: 115
- }, {
- rule: 'optional',
- type: 'BlacklistCommand',
- name: 'blacklistMessage',
- id: 116
- }]
- }],
- enums: [{
- name: 'CommandType',
- syntax: 'proto2',
- values: [{
- name: 'session',
- id: 0
- }, {
- name: 'conv',
- id: 1
- }, {
- name: 'direct',
- id: 2
- }, {
- name: 'ack',
- id: 3
- }, {
- name: 'rcp',
- id: 4
- }, {
- name: 'unread',
- id: 5
- }, {
- name: 'logs',
- id: 6
- }, {
- name: 'error',
- id: 7
- }, {
- name: 'login',
- id: 8
- }, {
- name: 'data',
- id: 9
- }, {
- name: 'room',
- id: 10
- }, {
- name: 'read',
- id: 11
- }, {
- name: 'presence',
- id: 12
- }, {
- name: 'report',
- id: 13
- }, {
- name: 'echo',
- id: 14
- }, {
- name: 'loggedin',
- id: 15
- }, {
- name: 'logout',
- id: 16
- }, {
- name: 'loggedout',
- id: 17
- }, {
- name: 'patch',
- id: 18
- }, {
- name: 'pubsub',
- id: 19
- }, {
- name: 'blacklist',
- id: 20
- }, {
- name: 'goaway',
- id: 21
- }]
- }, {
- name: 'OpType',
- syntax: 'proto2',
- values: [{
- name: 'open',
- id: 1
- }, {
- name: 'add',
- id: 2
- }, {
- name: 'remove',
- id: 3
- }, {
- name: 'close',
- id: 4
- }, {
- name: 'opened',
- id: 5
- }, {
- name: 'closed',
- id: 6
- }, {
- name: 'query',
- id: 7
- }, {
- name: 'query_result',
- id: 8
- }, {
- name: 'conflict',
- id: 9
- }, {
- name: 'added',
- id: 10
- }, {
- name: 'removed',
- id: 11
- }, {
- name: 'refresh',
- id: 12
- }, {
- name: 'refreshed',
- id: 13
- }, {
- name: 'start',
- id: 30
- }, {
- name: 'started',
- id: 31
- }, {
- name: 'joined',
- id: 32
- }, {
- name: 'members_joined',
- id: 33
- }, {
- name: 'left',
- id: 39
- }, {
- name: 'members_left',
- id: 40
- }, {
- name: 'results',
- id: 42
- }, {
- name: 'count',
- id: 43
- }, {
- name: 'result',
- id: 44
- }, {
- name: 'update',
- id: 45
- }, {
- name: 'updated',
- id: 46
- }, {
- name: 'mute',
- id: 47
- }, {
- name: 'unmute',
- id: 48
- }, {
- name: 'status',
- id: 49
- }, {
- name: 'members',
- id: 50
- }, {
- name: 'max_read',
- id: 51
- }, {
- name: 'is_member',
- id: 52
- }, {
- name: 'member_info_update',
- id: 53
- }, {
- name: 'member_info_updated',
- id: 54
- }, {
- name: 'member_info_changed',
- id: 55
- }, {
- name: 'join',
- id: 80
- }, {
- name: 'invite',
- id: 81
- }, {
- name: 'leave',
- id: 82
- }, {
- name: 'kick',
- id: 83
- }, {
- name: 'reject',
- id: 84
- }, {
- name: 'invited',
- id: 85
- }, {
- name: 'kicked',
- id: 86
- }, {
- name: 'upload',
- id: 100
- }, {
- name: 'uploaded',
- id: 101
- }, {
- name: 'subscribe',
- id: 120
- }, {
- name: 'subscribed',
- id: 121
- }, {
- name: 'unsubscribe',
- id: 122
- }, {
- name: 'unsubscribed',
- id: 123
- }, {
- name: 'is_subscribed',
- id: 124
- }, {
- name: 'modify',
- id: 150
- }, {
- name: 'modified',
- id: 151
- }, {
- name: 'block',
- id: 170
- }, {
- name: 'unblock',
- id: 171
- }, {
- name: 'blocked',
- id: 172
- }, {
- name: 'unblocked',
- id: 173
- }, {
- name: 'members_blocked',
- id: 174
- }, {
- name: 'members_unblocked',
- id: 175
- }, {
- name: 'check_block',
- id: 176
- }, {
- name: 'check_result',
- id: 177
- }, {
- name: 'add_shutup',
- id: 180
- }, {
- name: 'remove_shutup',
- id: 181
- }, {
- name: 'query_shutup',
- id: 182
- }, {
- name: 'shutup_added',
- id: 183
- }, {
- name: 'shutup_removed',
- id: 184
- }, {
- name: 'shutup_result',
- id: 185
- }, {
- name: 'shutuped',
- id: 186
- }, {
- name: 'unshutuped',
- id: 187
- }, {
- name: 'members_shutuped',
- id: 188
- }, {
- name: 'members_unshutuped',
- id: 189
- }, {
- name: 'check_shutup',
- id: 190
- }]
- }, {
- name: 'StatusType',
- syntax: 'proto2',
- values: [{
- name: 'on',
- id: 1
- }, {
- name: 'off',
- id: 2
- }]
- }],
- isNamespace: true
- }).build();
- const {
- JsonObjectMessage,
- UnreadTuple,
- LogItem,
- DataCommand,
- SessionCommand,
- ErrorCommand,
- DirectCommand,
- AckCommand,
- UnreadCommand,
- ConvCommand,
- RoomCommand,
- LogsCommand,
- RcpCommand,
- ReadTuple,
- MaxReadTuple,
- ReadCommand,
- PresenceCommand,
- ReportCommand,
- GenericCommand,
- BlacklistCommand,
- PatchCommand,
- PatchItem,
- ConvMemberInfo,
- CommandType,
- OpType,
- StatusType
- } = messageCompiled.push_server.messages2;
- var message = /*#__PURE__*/Object.freeze({
- __proto__: null,
- JsonObjectMessage: JsonObjectMessage,
- UnreadTuple: UnreadTuple,
- LogItem: LogItem,
- DataCommand: DataCommand,
- SessionCommand: SessionCommand,
- ErrorCommand: ErrorCommand,
- DirectCommand: DirectCommand,
- AckCommand: AckCommand,
- UnreadCommand: UnreadCommand,
- ConvCommand: ConvCommand,
- RoomCommand: RoomCommand,
- LogsCommand: LogsCommand,
- RcpCommand: RcpCommand,
- ReadTuple: ReadTuple,
- MaxReadTuple: MaxReadTuple,
- ReadCommand: ReadCommand,
- PresenceCommand: PresenceCommand,
- ReportCommand: ReportCommand,
- GenericCommand: GenericCommand,
- BlacklistCommand: BlacklistCommand,
- PatchCommand: PatchCommand,
- PatchItem: PatchItem,
- ConvMemberInfo: ConvMemberInfo,
- CommandType: CommandType,
- OpType: OpType,
- StatusType: StatusType
- });
- function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
- var desc = {};
- Object.keys(descriptor).forEach(function (key) {
- desc[key] = descriptor[key];
- });
- desc.enumerable = !!desc.enumerable;
- desc.configurable = !!desc.configurable;
- if ('value' in desc || desc.initializer) {
- desc.writable = true;
- }
- desc = decorators.slice().reverse().reduce(function (desc, decorator) {
- return decorator(target, property, desc) || desc;
- }, desc);
- if (context && desc.initializer !== void 0) {
- desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
- desc.initializer = undefined;
- }
- if (desc.initializer === void 0) {
- Object.defineProperty(target, property, desc);
- desc = null;
- }
- return desc;
- }
- const adapters = {};
- const getAdapter = name => {
- const adapter = adapters[name];
- if (adapter === undefined) {
- throw new Error(`${name} adapter is not configured`);
- }
- return adapter;
- };
- /**
- * 指定 Adapters
- * @function
- * @memberof module:leancloud-realtime
- * @param {Adapters} newAdapters Adapters 的类型请参考 {@link https://url.leanapp.cn/adapter-type-definitions @leancloud/adapter-types} 中的定义
- */
- const setAdapters = newAdapters => {
- Object.assign(adapters, newAdapters);
- };
- /* eslint-disable */
- var global$1 = typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : {};
- const EXPIRED = Symbol('expired');
- const debug = d('LC:Expirable');
- class Expirable {
- constructor(value, ttl) {
- this.originalValue = value;
- if (typeof ttl === 'number') {
- this.expiredAt = Date.now() + ttl;
- }
- }
- get value() {
- const expired = this.expiredAt && this.expiredAt <= Date.now();
- if (expired) debug(`expired: ${this.originalValue}`);
- return expired ? EXPIRED : this.originalValue;
- }
- }
- Expirable.EXPIRED = EXPIRED;
- const debug$1 = d('LC:Cache');
- class Cache {
- constructor(name = 'anonymous') {
- this.name = name;
- this._map = {};
- }
- get(key) {
- const cache = this._map[key];
- if (cache) {
- const {
- value
- } = cache;
- if (value !== Expirable.EXPIRED) {
- debug$1('[%s] hit: %s', this.name, key);
- return value;
- }
- delete this._map[key];
- }
- debug$1(`[${this.name}] missed: ${key}`);
- return null;
- }
- set(key, value, ttl) {
- debug$1('[%s] set: %s %d', this.name, key, ttl);
- this._map[key] = new Expirable(value, ttl);
- }
- }
- /**
- * 调试日志控制器
- * @const
- * @memberof module:leancloud-realtime
- * @example
- * debug.enable(); // 启用调试日志
- * debug.disable(); // 关闭调试日志
- */
- const debug$2 = {
- enable: (namespaces = 'LC*') => d.enable(namespaces),
- disable: d.disable
- };
- const tryAll = promiseConstructors => {
- const promise = new Promise(promiseConstructors[0]);
- if (promiseConstructors.length === 1) {
- return promise;
- }
- return promise.catch(() => tryAll(promiseConstructors.slice(1)));
- }; // eslint-disable-next-line no-sequences
- const tap = interceptor => value => (interceptor(value), value);
- const finalize = callback => [// eslint-disable-next-line no-sequences
- value => (callback(), value), error => {
- callback();
- throw error;
- }];
- /**
- * 将对象转换为 Date,支持 string、number、ProtoBuf Long 以及 LeanCloud 的 Date 类型,
- * 其他情况下(包括对象为 falsy)返回原值。
- * @private
- */
- const decodeDate = date => {
- if (!date) return date;
- if (typeof date === 'string' || typeof date === 'number') {
- return new Date(date);
- }
- if (date.__type === 'Date' && date.iso) {
- return new Date(date.iso);
- } // Long
- if (typeof date.toNumber === 'function') {
- return new Date(date.toNumber());
- }
- return date;
- };
- /**
- * 获取 Date 的毫秒数,如果不是一个 Date 返回 undefined。
- * @private
- */
- const getTime = date => date && date.getTime ? date.getTime() : undefined;
- /**
- * 解码对象中的 LeanCloud 数据结构。
- * 目前仅会处理 Date 类型。
- * @private
- */
- const decode = value => {
- if (!value) return value;
- if (value.__type === 'Date' && value.iso) {
- return new Date(value.iso);
- }
- if (Array.isArray(value)) {
- return value.map(decode);
- }
- if (isPlainObject(value)) {
- return Object.keys(value).reduce((result, key) => ({ ...result,
- [key]: decode(value[key])
- }), {});
- }
- return value;
- };
- /**
- * 将对象中的特殊类型编码为 LeanCloud 数据结构。
- * 目前仅会处理 Date 类型。
- * @private
- */
- const encode = value => {
- if (value instanceof Date) return {
- __type: 'Date',
- iso: value.toJSON()
- };
- if (Array.isArray(value)) {
- return value.map(encode);
- }
- if (isPlainObject(value)) {
- return Object.keys(value).reduce((result, key) => ({ ...result,
- [key]: encode(value[key])
- }), {});
- }
- return value;
- };
- const keyRemap = (keymap, obj) => Object.keys(obj).reduce((newObj, key) => {
- const newKey = keymap[key] || key;
- return Object.assign(newObj, {
- [newKey]: obj[key]
- });
- }, {});
- const isIE10 = global$1.navigator && global$1.navigator.userAgent && global$1.navigator.userAgent.indexOf('MSIE 10.') !== -1;
- /* eslint-disable no-proto */
- const getStaticProperty = (klass, property) => klass[property] || (klass.__proto__ ? getStaticProperty(klass.__proto__, property) : undefined);
- /* eslint-enable no-proto */
- const union = (a, b) => Array.from(new Set([...a, ...b]));
- const difference = (a, b) => Array.from((bSet => new Set(a.filter(x => !bSet.has(x))))(new Set(b)));
- const map = new WeakMap(); // protected property helper
- const internal = object => {
- if (!map.has(object)) {
- map.set(object, {});
- }
- return map.get(object);
- };
- const compact = (obj, filter) => {
- if (!isPlainObject(obj)) return obj;
- const object = { ...obj
- };
- Object.keys(object).forEach(prop => {
- const value = object[prop];
- if (value === filter) {
- delete object[prop];
- } else {
- object[prop] = compact(value, filter);
- }
- });
- return object;
- }; // debug utility
- const removeNull = obj => compact(obj, null);
- const trim = message => removeNull(JSON.parse(JSON.stringify(message)));
- const ensureArray = target => {
- if (Array.isArray(target)) {
- return target;
- }
- if (target === undefined || target === null) {
- return [];
- }
- return [target];
- };
- const setValue = (target, key, value) => {
- // '.' is not allowed in Class keys, escaping is not in concern now.
- const segs = key.split('.');
- const lastSeg = segs.pop();
- let currentTarget = target;
- segs.forEach(seg => {
- if (currentTarget[seg] === undefined) currentTarget[seg] = {};
- currentTarget = currentTarget[seg];
- });
- currentTarget[lastSeg] = value;
- return target;
- };
- const isWeapp = // eslint-disable-next-line no-undef
- typeof wx === 'object' && typeof wx.connectSocket === 'function'; // throttle decorator
- const throttle = wait => (target, property, descriptor) => {
- const callback = descriptor.value; // very naive, internal use only
- if (callback.length) {
- throw new Error('throttled function should not accept any arguments');
- }
- return { ...descriptor,
- value() {
- let {
- throttleMeta
- } = internal(this);
- if (!throttleMeta) {
- throttleMeta = {};
- internal(this).throttleMeta = throttleMeta;
- }
- let {
- [property]: propertyMeta
- } = throttleMeta;
- if (!propertyMeta) {
- propertyMeta = {};
- throttleMeta[property] = propertyMeta;
- }
- const {
- previouseTimestamp = 0,
- timeout
- } = propertyMeta;
- const now = Date.now();
- const remainingTime = wait - (now - previouseTimestamp);
- if (remainingTime <= 0) {
- throttleMeta[property].previouseTimestamp = now;
- callback.apply(this);
- } else if (!timeout) {
- propertyMeta.timeout = setTimeout(() => {
- propertyMeta.previouseTimestamp = Date.now();
- delete propertyMeta.timeout;
- callback.apply(this);
- }, remainingTime);
- }
- }
- };
- };
- const isCNApp = appId => appId.slice(-9) !== '-MdYXbMMI';
- const equalBuffer = (buffer1, buffer2) => {
- if (!buffer1 || !buffer2) return false;
- if (buffer1.byteLength !== buffer2.byteLength) return false;
- const a = new Uint8Array(buffer1);
- const b = new Uint8Array(buffer2);
- return !a.some((value, index) => value !== b[index]);
- };
- var _class;
- const debug$3 = d('LC:WebSocketPlus');
- const OPEN = 'open';
- const DISCONNECT = 'disconnect';
- const RECONNECT = 'reconnect';
- const RETRY = 'retry';
- const SCHEDULE = 'schedule';
- const OFFLINE = 'offline';
- const ONLINE = 'online';
- const ERROR = 'error';
- const MESSAGE = 'message';
- const HEARTBEAT_TIME = 180000;
- const TIMEOUT_TIME = 380000;
- const DEFAULT_RETRY_STRATEGY = attempt => Math.min(1000 * 2 ** attempt, 300000);
- const requireConnected = (target, name, descriptor) => ({ ...descriptor,
- value: function requireConnectedWrapper(...args) {
- this.checkConnectionAvailability(name);
- return descriptor.value.call(this, ...args);
- }
- });
- let WebSocketPlus = (_class = class WebSocketPlus extends EventEmitter {
- get urls() {
- return this._urls;
- }
- set urls(urls) {
- this._urls = ensureArray(urls);
- }
- constructor(getUrls, protocol) {
- super();
- this.init();
- this._protocol = protocol;
- Promise.resolve(typeof getUrls === 'function' ? getUrls() : getUrls).then(ensureArray).then(urls => {
- this._urls = urls;
- return this._open();
- }).then(() => {
- this.__postponeTimeoutTimer = this._postponeTimeoutTimer.bind(this);
- if (global$1.addEventListener) {
- this.__pause = () => {
- if (this.can('pause')) this.pause();
- };
- this.__resume = () => {
- if (this.can('resume')) this.resume();
- };
- global$1.addEventListener('offline', this.__pause);
- global$1.addEventListener('online', this.__resume);
- }
- this.open();
- }).catch(this.throw.bind(this));
- }
- _open() {
- return this._createWs(this._urls, this._protocol).then(ws => {
- const [first, ...reset] = this._urls;
- this._urls = [...reset, first];
- return ws;
- });
- }
- _createWs(urls, protocol) {
- return tryAll(urls.map(url => (resolve, reject) => {
- debug$3(`connect [${url}] ${protocol}`);
- const WebSocket = getAdapter('WebSocket');
- const ws = protocol ? new WebSocket(url, protocol) : new WebSocket(url);
- ws.binaryType = this.binaryType || 'arraybuffer';
- ws.onopen = () => resolve(ws);
- ws.onclose = error => {
- if (error instanceof Error) {
- return reject(error);
- } // in browser, error event is useless
- return reject(new Error(`Failed to connect [${url}]`));
- };
- ws.onerror = ws.onclose;
- })).then(ws => {
- this._ws = ws;
- this._ws.onclose = this._handleClose.bind(this);
- this._ws.onmessage = this._handleMessage.bind(this);
- return ws;
- });
- }
- _destroyWs() {
- const ws = this._ws;
- if (!ws) return;
- ws.onopen = null;
- ws.onclose = null;
- ws.onerror = null;
- ws.onmessage = null;
- this._ws = null;
- ws.close();
- } // eslint-disable-next-line class-methods-use-this
- onbeforeevent(event, from, to, ...payload) {
- debug$3(`${event}: ${from} -> ${to} %o`, payload);
- }
- onopen() {
- this.emit(OPEN);
- }
- onconnected() {
- this._startConnectionKeeper();
- }
- onleaveconnected(event, from, to) {
- this._stopConnectionKeeper();
- this._destroyWs();
- if (to === 'offline' || to === 'disconnected') {
- this.emit(DISCONNECT);
- }
- }
- onpause() {
- this.emit(OFFLINE);
- }
- onbeforeresume() {
- this.emit(ONLINE);
- }
- onreconnect() {
- this.emit(RECONNECT);
- }
- ondisconnected(event, from, to, attempt = 0) {
- const delay = from === OFFLINE ? 0 : DEFAULT_RETRY_STRATEGY.call(null, attempt);
- debug$3(`schedule attempt=${attempt} delay=${delay}`);
- this.emit(SCHEDULE, attempt, delay);
- if (this.__scheduledRetry) {
- clearTimeout(this.__scheduledRetry);
- }
- this.__scheduledRetry = setTimeout(() => {
- if (this.is('disconnected')) {
- this.retry(attempt);
- }
- }, delay);
- }
- onretry(event, from, to, attempt = 0) {
- this.emit(RETRY, attempt);
- this._open().then(() => this.can('reconnect') && this.reconnect(), () => this.can('fail') && this.fail(attempt + 1));
- }
- onerror(event, from, to, error) {
- this.emit(ERROR, error);
- }
- onclose() {
- if (global$1.removeEventListener) {
- if (this.__pause) global$1.removeEventListener('offline', this.__pause);
- if (this.__resume) global$1.removeEventListener('online', this.__resume);
- }
- }
- checkConnectionAvailability(name = 'API') {
- if (!this.is('connected')) {
- const currentState = this.current;
- console.warn(`${name} should not be called when the connection is ${currentState}`);
- if (this.is('disconnected') || this.is('reconnecting')) {
- console.warn('disconnect and reconnect event should be handled to avoid such calls.');
- }
- throw new Error('Connection unavailable');
- }
- } // jsdoc-ignore-start
- // jsdoc-ignore-end
- _ping() {
- debug$3('ping');
- try {
- this.ping();
- } catch (error) {
- console.warn(`websocket ping error: ${error.message}`);
- }
- }
- ping() {
- if (this._ws.ping) {
- this._ws.ping();
- } else {
- console.warn(`The WebSocket implement does not support sending ping frame.
- Override ping method to use application defined ping/pong mechanism.`);
- }
- }
- _postponeTimeoutTimer() {
- debug$3('_postponeTimeoutTimer');
- this._clearTimeoutTimers();
- this._timeoutTimer = setTimeout(() => {
- debug$3('timeout');
- this.disconnect();
- }, TIMEOUT_TIME);
- }
- _clearTimeoutTimers() {
- if (this._timeoutTimer) {
- clearTimeout(this._timeoutTimer);
- }
- }
- _startConnectionKeeper() {
- debug$3('start connection keeper');
- this._heartbeatTimer = setInterval(this._ping.bind(this), HEARTBEAT_TIME);
- const addListener = this._ws.addListener || this._ws.addEventListener;
- if (!addListener) {
- debug$3('connection keeper disabled due to the lack of #addEventListener.');
- return;
- }
- addListener.call(this._ws, 'message', this.__postponeTimeoutTimer);
- addListener.call(this._ws, 'pong', this.__postponeTimeoutTimer);
- this._postponeTimeoutTimer();
- }
- _stopConnectionKeeper() {
- debug$3('stop connection keeper'); // websockets/ws#489
- const removeListener = this._ws.removeListener || this._ws.removeEventListener;
- if (removeListener) {
- removeListener.call(this._ws, 'message', this.__postponeTimeoutTimer);
- removeListener.call(this._ws, 'pong', this.__postponeTimeoutTimer);
- this._clearTimeoutTimers();
- }
- if (this._heartbeatTimer) {
- clearInterval(this._heartbeatTimer);
- }
- }
- _handleClose(event) {
- debug$3(`ws closed [${event.code}] ${event.reason}`); // socket closed manually, ignore close event.
- if (this.isFinished()) return;
- this.handleClose(event);
- }
- handleClose() {
- // reconnect
- this.disconnect();
- } // jsdoc-ignore-start
- // jsdoc-ignore-end
- send(data) {
- debug$3('send', data);
- this._ws.send(data);
- }
- _handleMessage(event) {
- debug$3('message', event.data);
- this.handleMessage(event.data);
- }
- handleMessage(message) {
- this.emit(MESSAGE, message);
- }
- }, (_applyDecoratedDescriptor(_class.prototype, "_ping", [requireConnected], Object.getOwnPropertyDescriptor(_class.prototype, "_ping"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "send", [requireConnected], Object.getOwnPropertyDescriptor(_class.prototype, "send"), _class.prototype)), _class);
- StateMachine.create({
- target: WebSocketPlus.prototype,
- initial: {
- state: 'initialized',
- event: 'init',
- defer: true
- },
- terminal: 'closed',
- events: [{
- name: 'open',
- from: 'initialized',
- to: 'connected'
- }, {
- name: 'disconnect',
- from: 'connected',
- to: 'disconnected'
- }, {
- name: 'retry',
- from: 'disconnected',
- to: 'reconnecting'
- }, {
- name: 'fail',
- from: 'reconnecting',
- to: 'disconnected'
- }, {
- name: 'reconnect',
- from: 'reconnecting',
- to: 'connected'
- }, {
- name: 'pause',
- from: ['connected', 'disconnected', 'reconnecting'],
- to: 'offline'
- }, {}, {
- name: 'resume',
- from: 'offline',
- to: 'disconnected'
- }, {
- name: 'close',
- from: ['connected', 'disconnected', 'reconnecting', 'offline'],
- to: 'closed'
- }, {
- name: 'throw',
- from: '*',
- to: 'error'
- }]
- });
- const error = Object.freeze({
- 1000: {
- name: 'CLOSE_NORMAL'
- },
- 1006: {
- name: 'CLOSE_ABNORMAL'
- },
- 4100: {
- name: 'APP_NOT_AVAILABLE',
- message: 'App not exists or realtime message service is disabled.'
- },
- 4102: {
- name: 'SIGNATURE_FAILED',
- message: 'Login signature mismatch.'
- },
- 4103: {
- name: 'INVALID_LOGIN',
- message: 'Malformed clientId.'
- },
- 4105: {
- name: 'SESSION_REQUIRED',
- message: 'Message sent before session opened.'
- },
- 4107: {
- name: 'READ_TIMEOUT'
- },
- 4108: {
- name: 'LOGIN_TIMEOUT'
- },
- 4109: {
- name: 'FRAME_TOO_LONG'
- },
- 4110: {
- name: 'INVALID_ORIGIN',
- message: 'Access denied by domain whitelist.'
- },
- 4111: {
- name: 'SESSION_CONFLICT'
- },
- 4112: {
- name: 'SESSION_TOKEN_EXPIRED'
- },
- 4113: {
- name: 'APP_QUOTA_EXCEEDED',
- message: 'The daily active users limit exceeded.'
- },
- 4116: {
- name: 'MESSAGE_SENT_QUOTA_EXCEEDED',
- message: 'Command sent too fast.'
- },
- 4200: {
- name: 'INTERNAL_ERROR',
- message: 'Internal error, please contact LeanCloud for support.'
- },
- 4301: {
- name: 'CONVERSATION_API_FAILED',
- message: 'Upstream Conversatoin API failed, see error.detail for details.'
- },
- 4302: {
- name: 'CONVERSATION_SIGNATURE_FAILED',
- message: 'Conversation action signature mismatch.'
- },
- 4303: {
- name: 'CONVERSATION_NOT_FOUND'
- },
- 4304: {
- name: 'CONVERSATION_FULL'
- },
- 4305: {
- name: 'CONVERSATION_REJECTED_BY_APP',
- message: 'Conversation action rejected by hook.'
- },
- 4306: {
- name: 'CONVERSATION_UPDATE_FAILED'
- },
- 4307: {
- name: 'CONVERSATION_READ_ONLY'
- },
- 4308: {
- name: 'CONVERSATION_NOT_ALLOWED'
- },
- 4309: {
- name: 'CONVERSATION_UPDATE_REJECTED',
- message: 'Conversation update rejected because the client is not a member.'
- },
- 4310: {
- name: 'CONVERSATION_QUERY_FAILED',
- message: 'Conversation query failed because it is too expansive.'
- },
- 4311: {
- name: 'CONVERSATION_LOG_FAILED'
- },
- 4312: {
- name: 'CONVERSATION_LOG_REJECTED',
- message: 'Message query rejected because the client is not a member of the conversation.'
- },
- 4313: {
- name: 'SYSTEM_CONVERSATION_REQUIRED'
- },
- 4314: {
- name: 'NORMAL_CONVERSATION_REQUIRED'
- },
- 4315: {
- name: 'CONVERSATION_BLACKLISTED',
- message: 'Blacklisted in the conversation.'
- },
- 4316: {
- name: 'TRANSIENT_CONVERSATION_REQUIRED'
- },
- 4317: {
- name: 'CONVERSATION_MEMBERSHIP_REQUIRED'
- },
- 4318: {
- name: 'CONVERSATION_API_QUOTA_EXCEEDED',
- message: 'LeanCloud API quota exceeded. You may upgrade your plan.'
- },
- 4323: {
- name: 'TEMPORARY_CONVERSATION_EXPIRED',
- message: 'Temporary conversation expired or does not exist.'
- },
- 4401: {
- name: 'INVALID_MESSAGING_TARGET',
- message: 'Conversation does not exist or client is not a member.'
- },
- 4402: {
- name: 'MESSAGE_REJECTED_BY_APP',
- message: 'Message rejected by hook.'
- },
- 4403: {
- name: 'MESSAGE_OWNERSHIP_REQUIRED'
- },
- 4404: {
- name: 'MESSAGE_NOT_FOUND'
- },
- 4405: {
- name: 'MESSAGE_UPDATE_REJECTED_BY_APP',
- message: 'Message update rejected by hook.'
- },
- 4406: {
- name: 'MESSAGE_EDIT_DISABLED'
- },
- 4407: {
- name: 'MESSAGE_RECALL_DISABLED'
- },
- 5130: {
- name: 'OWNER_PROMOTION_NOT_ALLOWED',
- message: "Updating a member's role to owner is not allowed."
- }
- });
- const ErrorCode = Object.freeze(Object.keys(error).reduce((result, code) => Object.assign(result, {
- [error[code].name]: Number(code)
- }), {}));
- const createError = ({
- code,
- reason,
- appCode,
- detail,
- error: errorMessage
- }) => {
- let message = reason || detail || errorMessage;
- let name = reason;
- if (!message && error[code]) {
- ({
- name
- } = error[code]);
- message = error[code].message || name;
- }
- if (!message) {
- message = `Unknow Error: ${code}`;
- }
- const err = new Error(message);
- return Object.assign(err, {
- code,
- appCode,
- detail,
- name
- });
- };
- const debug$4 = d('LC:Connection');
- const COMMAND_TIMEOUT = 20000;
- const EXPIRE = Symbol('expire');
- const isIdempotentCommand = command => !(command.cmd === CommandType.direct || command.cmd === CommandType.session && command.op === OpType.open || command.cmd === CommandType.conv && (command.op === OpType.start || command.op === OpType.update || command.op === OpType.members));
- class Connection extends WebSocketPlus {
- constructor(getUrl, {
- format,
- version
- }) {
- debug$4('initializing Connection');
- const protocolString = `lc.${format}.${version}`;
- super(getUrl, protocolString);
- this._protocolFormat = format;
- this._commands = {};
- this._serialId = 0;
- }
- async send(command, waitingForRespond = true) {
- let buffer;
- let serialId;
- if (waitingForRespond) {
- if (isIdempotentCommand(command)) {
- buffer = command.toArrayBuffer();
- const duplicatedCommand = values(this._commands).find(({
- buffer: targetBuffer,
- command: targetCommand
- }) => targetCommand.cmd === command.cmd && targetCommand.op === command.op && equalBuffer(targetBuffer, buffer));
- if (duplicatedCommand) {
- console.warn(`Duplicated command [cmd:${command.cmd} op:${command.op}] is throttled.`);
- return duplicatedCommand.promise;
- }
- }
- this._serialId += 1;
- serialId = this._serialId;
- command.i = serialId; // eslint-disable-line no-param-reassign
- }
- if (debug$4.enabled) debug$4('↑ %O sent', trim(command));
- let message;
- if (this._protocolFormat === 'proto2base64') {
- message = command.toBase64();
- } else if (command.toArrayBuffer) {
- message = command.toArrayBuffer();
- }
- if (!message) {
- throw new TypeError(`${command} is not a GenericCommand`);
- }
- super.send(message);
- if (!waitingForRespond) return undefined;
- const promise = new Promise((resolve, reject) => {
- this._commands[serialId] = {
- command,
- buffer,
- resolve,
- reject,
- timeout: setTimeout(() => {
- if (this._commands[serialId]) {
- if (debug$4.enabled) debug$4('✗ %O timeout', trim(command));
- reject(createError({
- error: `Command Timeout [cmd:${command.cmd} op:${command.op}]`,
- name: 'COMMAND_TIMEOUT'
- }));
- delete this._commands[serialId];
- }
- }, COMMAND_TIMEOUT)
- };
- });
- this._commands[serialId].promise = promise;
- return promise;
- }
- handleMessage(msg) {
- let message;
- try {
- message = GenericCommand.decode(msg);
- if (debug$4.enabled) debug$4('↓ %O received', trim(message));
- } catch (e) {
- console.warn('Decode message failed:', e.message, msg);
- return;
- }
- const serialId = message.i;
- if (serialId) {
- if (this._commands[serialId]) {
- clearTimeout(this._commands[serialId].timeout);
- if (message.cmd === CommandType.error) {
- this._commands[serialId].reject(createError(message.errorMessage));
- } else {
- this._commands[serialId].resolve(message);
- }
- delete this._commands[serialId];
- } else {
- console.warn(`Unexpected command received with serialId [${serialId}],
- which have timed out or never been requested.`);
- }
- } else {
- switch (message.cmd) {
- case CommandType.error:
- {
- this.emit(ERROR, createError(message.errorMessage));
- return;
- }
- case CommandType.goaway:
- {
- this.emit(EXPIRE);
- return;
- }
- default:
- {
- this.emit(MESSAGE, message);
- }
- }
- }
- }
- ping() {
- return this.send(new GenericCommand({
- cmd: CommandType.echo
- })).catch(error => debug$4('ping failed:', error));
- }
- }
- const debug$5 = d('LC:request');
- var request = (({
- method = 'GET',
- url: _url,
- query,
- headers,
- data,
- timeout: time
- }) => {
- let url = _url;
- if (query) {
- const queryString = Object.keys(query).map(key => {
- const value = query[key];
- if (value === undefined) return undefined;
- const v = isPlainObject(value) ? JSON.stringify(value) : value;
- return `${encodeURIComponent(key)}=${encodeURIComponent(v)}`;
- }).filter(qs => qs).join('&');
- url = `${url}?${queryString}`;
- }
- debug$5('Req: %O %O %O', method, url, {
- headers,
- data
- });
- const request = getAdapter('request');
- const promise = request(url, {
- method,
- headers,
- data
- }).then(response => {
- if (response.ok === false) {
- const error = createError(response.data);
- error.response = response;
- throw error;
- }
- debug$5('Res: %O %O %O', url, response.status, response.data);
- return response.data;
- }).catch(error => {
- if (error.response) {
- debug$5('Error: %O %O %O', url, error.response.status, error.response.data);
- }
- throw error;
- });
- return time ? promiseTimeout.timeout(promise, time) : promise;
- });
- /* eslint-disable max-len */
- const checkType = middleware => param => {
- const {
- constructor
- } = param;
- return Promise.resolve(param).then(middleware).then(tap(result => {
- if (result === undefined || result === null) {
- // eslint-disable-next-line max-len
- return console.warn(`Middleware[${middleware._pluginName || 'anonymous plugin'}:${middleware.name || 'anonymous middleware'}] param/return types not match. It returns ${result} while a ${param.constructor.name} expected.`);
- }
- if (!(result instanceof constructor)) {
- // eslint-disable-next-line max-len
- return console.warn(`Middleware[${middleware._pluginName || 'anonymous plugin'}:${middleware.name || 'anonymous middleware'}] param/return types not match. It returns a ${result.constructor.name} while a ${param.constructor.name} expected.`);
- }
- return 0;
- }));
- };
- const applyDecorators = (decorators, target) => {
- if (decorators) {
- decorators.forEach(decorator => {
- try {
- decorator(target);
- } catch (error) {
- if (decorator._pluginName) {
- error.message += `[${decorator._pluginName}]`;
- }
- throw error;
- }
- });
- }
- };
- const applyMiddlewares = middlewares => target => ensureArray(middlewares).reduce((previousPromise, middleware) => previousPromise.then(checkType(middleware)).catch(error => {
- if (middleware._pluginName) {
- // eslint-disable-next-line no-param-reassign
- error.message += `[${middleware._pluginName}]`;
- }
- throw error;
- }), Promise.resolve(target));
- const applyDispatcher = (dispatchers, payload) => ensureArray(dispatchers).reduce((resultPromise, dispatcher) => resultPromise.then(shouldDispatch => shouldDispatch === false ? false : dispatcher(...payload)).catch(error => {
- if (dispatcher._pluginName) {
- // eslint-disable-next-line no-param-reassign
- error.message += `[${dispatcher._pluginName}]`;
- }
- throw error;
- }), Promise.resolve(true));
- var version = "5.0.0-rc.7";
- // eslint-disable-next-line max-classes-per-file
- const debug$6 = d('LC:Realtime');
- const routerCache = new Cache('push-router');
- const initializedApp = {};
- class Realtime extends EventEmitter {
- /**
- * @extends EventEmitter
- * @param {Object} options
- * @param {String} options.appId
- * @param {String} options.appKey (since 4.0.0)
- * @param {String|Object} [options.server] 指定服务器域名,中国节点应用此参数必填(since 4.0.0)
- * @param {Boolean} [options.noBinary=false] 设置 WebSocket 使用字符串格式收发消息(默认为二进制格式)。
- * 适用于 WebSocket 实现不支持二进制数据格式的情况
- * @param {Boolean} [options.ssl=true] 使用 wss 进行连接
- * @param {String|String[]} [options.RTMServers] 指定私有部署的 RTM 服务器地址(since 4.0.0)
- * @param {Plugin[]} [options.plugins] 加载插件(since 3.1.0)
- */
- constructor({
- plugins,
- ...options
- }) {
- debug$6('initializing Realtime %s %O', version, options);
- super();
- const {
- appId
- } = options;
- if (typeof appId !== 'string') {
- throw new TypeError(`appId [${appId}] is not a string`);
- }
- if (initializedApp[appId]) {
- throw new Error(`App [${appId}] is already initialized.`);
- }
- initializedApp[appId] = true;
- if (typeof options.appKey !== 'string') {
- throw new TypeError(`appKey [${options.appKey}] is not a string`);
- }
- if (isCNApp(appId)) {
- if (!options.server) {
- throw new TypeError(`server option is required for apps from CN region`);
- }
- }
- this._options = {
- appId: undefined,
- appKey: undefined,
- noBinary: false,
- ssl: true,
- RTMServerName: typeof process !== 'undefined' ? process.env.RTM_SERVER_NAME : undefined,
- // undocumented on purpose, internal use only
- ...options
- };
- this._cache = new Cache('endpoints');
- const _this = internal(this);
- _this.clients = new Set();
- _this.pendingClients = new Set();
- const mergedPlugins = [...ensureArray(Realtime.__preRegisteredPlugins), ...ensureArray(plugins)];
- debug$6('Using plugins %o', mergedPlugins.map(plugin => plugin.name));
- this._plugins = mergedPlugins.reduce((result, plugin) => {
- Object.keys(plugin).forEach(hook => {
- if ({}.hasOwnProperty.call(plugin, hook) && hook !== 'name') {
- if (plugin.name) {
- ensureArray(plugin[hook]).forEach(value => {
- // eslint-disable-next-line no-param-reassign
- value._pluginName = plugin.name;
- });
- } // eslint-disable-next-line no-param-reassign
- result[hook] = ensureArray(result[hook]).concat(plugin[hook]);
- }
- });
- return result;
- }, {}); // onRealtimeCreate hook
- applyDecorators(this._plugins.onRealtimeCreate, this);
- }
- async _request({
- method,
- url: _url,
- version = '1.1',
- path,
- query,
- headers,
- data
- }) {
- let url = _url;
- if (!url) {
- const {
- appId,
- server
- } = this._options;
- const {
- api
- } = await this.constructor._getServerUrls({
- appId,
- server
- });
- url = `${api}/${version}${path}`;
- }
- return request({
- url,
- method,
- query,
- headers: {
- 'X-LC-Id': this._options.appId,
- 'X-LC-Key': this._options.appKey,
- ...headers
- },
- data
- });
- }
- _open() {
- if (this._openPromise) return this._openPromise;
- let format = 'protobuf2';
- if (this._options.noBinary) {
- // 不发送 binary data,fallback to base64 string
- format = 'proto2base64';
- }
- const version = 3;
- const protocol = {
- format,
- version
- };
- this._openPromise = new Promise((resolve, reject) => {
- debug$6('No connection established, create a new one.');
- const connection = new Connection(() => this._getRTMServers(this._options), protocol);
- connection.on(OPEN, () => resolve(connection)).on(ERROR, error => {
- delete this._openPromise;
- reject(error);
- }).on(EXPIRE, async () => {
- debug$6('Connection expired. Refresh endpoints.');
- this._cache.set('endpoints', null, 0);
- connection.urls = await this._getRTMServers(this._options);
- connection.disconnect();
- }).on(MESSAGE, this._dispatchCommand.bind(this));
- /**
- * 连接断开。
- * 连接断开可能是因为 SDK 进入了离线状态(see {@link Realtime#event:OFFLINE}),或长时间没有收到服务器心跳。
- * 连接断开后所有的网络操作都会失败,请在连接断开后禁用相关的 UI 元素。
- * @event Realtime#DISCONNECT
- */
- /**
- * 计划在一段时间后尝试重新连接
- * @event Realtime#SCHEDULE
- * @param {Number} attempt 尝试重连的次数
- * @param {Number} delay 延迟的毫秒数
- */
- /**
- * 正在尝试重新连接
- * @event Realtime#RETRY
- * @param {Number} attempt 尝试重连的次数
- */
- /**
- * 连接恢复正常。
- * 请重新启用在 {@link Realtime#event:DISCONNECT} 事件中禁用的相关 UI 元素
- * @event Realtime#RECONNECT
- */
- /**
- * 客户端连接断开
- * @event IMClient#DISCONNECT
- * @see Realtime#event:DISCONNECT
- * @since 3.2.0
- */
- /**
- * 计划在一段时间后尝试重新连接
- * @event IMClient#SCHEDULE
- * @param {Number} attempt 尝试重连的次数
- * @param {Number} delay 延迟的毫秒数
- * @since 3.2.0
- */
- /**
- * 正在尝试重新连接
- * @event IMClient#RETRY
- * @param {Number} attempt 尝试重连的次数
- * @since 3.2.0
- */
- /**
- * 客户端进入离线状态。
- * 这通常意味着网络已断开,或者 {@link Realtime#pause} 被调用
- * @event Realtime#OFFLINE
- * @since 3.4.0
- */
- /**
- * 客户端恢复在线状态
- * 这通常意味着网络已恢复,或者 {@link Realtime#resume} 被调用
- * @event Realtime#ONLINE
- * @since 3.4.0
- */
- /**
- * 进入离线状态。
- * 这通常意味着网络已断开,或者 {@link Realtime#pause} 被调用
- * @event IMClient#OFFLINE
- * @since 3.4.0
- */
- /**
- * 恢复在线状态
- * 这通常意味着网络已恢复,或者 {@link Realtime#resume} 被调用
- * @event IMClient#ONLINE
- * @since 3.4.0
- */
- // event proxy
- [DISCONNECT, RECONNECT, RETRY, SCHEDULE, OFFLINE, ONLINE].forEach(event => connection.on(event, (...payload) => {
- debug$6(`${event} event emitted. %o`, payload);
- this.emit(event, ...payload);
- if (event !== RECONNECT) {
- internal(this).clients.forEach(client => {
- client.emit(event, ...payload);
- });
- }
- })); // override handleClose
- connection.handleClose = function handleClose(event) {
- const isFatal = [ErrorCode.APP_NOT_AVAILABLE, ErrorCode.INVALID_LOGIN, ErrorCode.INVALID_ORIGIN].some(errorCode => errorCode === event.code);
- if (isFatal) {
- // in these cases, SDK should throw.
- this.throw(createError(event));
- } else {
- // reconnect
- this.disconnect();
- }
- };
- internal(this).connection = connection;
- });
- return this._openPromise;
- }
- async _getRTMServers(options) {
- if (options.RTMServers) return shuffle(ensureArray(options.RTMServers));
- let info;
- const cachedEndPoints = this._cache.get('endpoints');
- if (cachedEndPoints) {
- info = cachedEndPoints;
- } else {
- info = await this.constructor._fetchRTMServers(options);
- const {
- server,
- secondary,
- ttl
- } = info;
- if (typeof server !== 'string' && typeof secondary !== 'string' && typeof ttl !== 'number') {
- throw new Error(`malformed RTM route response: ${JSON.stringify(info)}`);
- }
- this._cache.set('endpoints', info, info.ttl * 1000);
- }
- debug$6('endpoint info: %O', info);
- return [info.server, info.secondary];
- }
- static async _getServerUrls({
- appId,
- server
- }) {
- debug$6('fetch server urls');
- if (server) {
- if (typeof server !== 'string') return server;
- return {
- RTMRouter: server,
- api: server
- };
- }
- const cachedRouter = routerCache.get(appId);
- if (cachedRouter) return cachedRouter;
- const defaultProtocol = 'https://';
- return request({
- url: 'https://app-router.com/2/route',
- query: {
- appId
- },
- timeout: 20000
- }).then(tap(debug$6)).then(({
- rtm_router_server: RTMRouterServer,
- api_server: APIServer,
- ttl = 3600
- }) => {
- if (!RTMRouterServer) {
- throw new Error('rtm router not exists');
- }
- const serverUrls = {
- RTMRouter: `${defaultProtocol}${RTMRouterServer}`,
- api: `${defaultProtocol}${APIServer}`
- };
- routerCache.set(appId, serverUrls, ttl * 1000);
- return serverUrls;
- }).catch(() => {
- const id = appId.slice(0, 8).toLowerCase();
- const domain = 'lncldglobal.com';
- return {
- RTMRouter: `${defaultProtocol}${id}.rtm.${domain}`,
- api: `${defaultProtocol}${id}.api.${domain}`
- };
- });
- }
- static _fetchRTMServers({
- appId,
- ssl,
- server,
- RTMServerName
- }) {
- debug$6('fetch endpoint info');
- return this._getServerUrls({
- appId,
- server
- }).then(tap(debug$6)).then(({
- RTMRouter
- }) => request({
- url: `${RTMRouter}/v1/route`,
- query: {
- appId,
- secure: ssl,
- features: isWeapp ? 'wechat' : undefined,
- server: RTMServerName,
- _t: Date.now()
- },
- timeout: 20000
- }).then(tap(debug$6)));
- }
- _close() {
- if (this._openPromise) {
- this._openPromise.then(connection => connection.close());
- }
- delete this._openPromise;
- }
- /**
- * 手动进行重连。
- * SDK 在网络出现异常时会自动按照一定的时间间隔尝试重连,调用该方法会立即尝试重连并重置重连尝试计数器。
- * 只能在 `SCHEDULE` 事件之后,`RETRY` 事件之前调用,如果当前网络正常或者正在进行重连,调用该方法会抛异常。
- */
- retry() {
- const {
- connection
- } = internal(this);
- if (!connection) {
- throw new Error('no connection established');
- }
- if (connection.cannot('retry')) {
- throw new Error(`retrying not allowed when not disconnected. the connection is now ${connection.current}`);
- }
- return connection.retry();
- }
- /**
- * 暂停,使 SDK 进入离线状态。
- * 你可以在网络断开、应用进入后台等时刻调用该方法让 SDK 进入离线状态,离线状态下不会尝试重连。
- * 在浏览器中 SDK 会自动监听网络变化,因此无需手动调用该方法。
- *
- * @since 3.4.0
- * @see Realtime#event:OFFLINE
- */
- pause() {
- // 这个方法常常在网络断开、进入后台时被调用,此时 connection 可能没有建立或者已经 close。
- // 因此不像 retry,这个方法应该尽可能 loose
- const {
- connection
- } = internal(this);
- if (!connection) return;
- if (connection.can('pause')) connection.pause();
- }
- /**
- * 恢复在线状态。
- * 你可以在网络恢复、应用回到前台等时刻调用该方法让 SDK 恢复在线状态,恢复在线状态后 SDK 会开始尝试重连。
- *
- * @since 3.4.0
- * @see Realtime#event:ONLINE
- */
- resume() {
- // 与 pause 一样,这个方法应该尽可能 loose
- const {
- connection
- } = internal(this);
- if (!connection) return;
- if (connection.can('resume')) connection.resume();
- }
- _registerPending(value) {
- internal(this).pendingClients.add(value);
- }
- _deregisterPending(client) {
- internal(this).pendingClients.delete(client);
- }
- _register(client) {
- internal(this).clients.add(client);
- }
- _deregister(client) {
- const _this = internal(this);
- _this.clients.delete(client);
- if (_this.clients.size + _this.pendingClients.size === 0) {
- this._close();
- }
- }
- _dispatchCommand(command) {
- return applyDispatcher(this._plugins.beforeCommandDispatch, [command, this]).then(shouldDispatch => {
- // no plugin handled this command
- if (shouldDispatch) return debug$6('[WARN] Unexpected message received: %O', trim(command));
- return false;
- });
- }
- } // For test purpose only
- const polyfilledPromise = Promise;
- // IMClient
- const UNREAD_MESSAGES_COUNT_UPDATE = 'unreadmessagescountupdate';
- const CLOSE = 'close';
- const CONFLICT = 'conflict';
- const CONVERSATION_INFO_UPDATED = 'conversationinfoupdated';
- const UNHANDLED_MESSAGE = 'unhandledmessage'; // shared
- const INVITED = 'invited';
- const KICKED = 'kicked';
- const MEMBERS_JOINED = 'membersjoined';
- const MEMBERS_LEFT = 'membersleft';
- const MEMBER_INFO_UPDATED = 'memberinfoupdated';
- const BLOCKED = 'blocked';
- const UNBLOCKED = 'unblocked';
- const MEMBERS_BLOCKED = 'membersblocked';
- const MEMBERS_UNBLOCKED = 'membersunblocked';
- const MUTED = 'muted';
- const UNMUTED = 'unmuted';
- const MEMBERS_MUTED = 'membersmuted';
- const MEMBERS_UNMUTED = 'membersunmuted';
- const MESSAGE$1 = 'message';
- const MESSAGE_RECALL = 'messagerecall';
- const MESSAGE_UPDATE = 'messageupdate'; // Conversation
- const LAST_DELIVERED_AT_UPDATE = 'lastdeliveredatupdate';
- const LAST_READ_AT_UPDATE = 'lastreadatupdate';
- const INFO_UPDATED = 'infoupdated';
- var IMEvent = /*#__PURE__*/Object.freeze({
- __proto__: null,
- UNREAD_MESSAGES_COUNT_UPDATE: UNREAD_MESSAGES_COUNT_UPDATE,
- CLOSE: CLOSE,
- CONFLICT: CONFLICT,
- CONVERSATION_INFO_UPDATED: CONVERSATION_INFO_UPDATED,
- UNHANDLED_MESSAGE: UNHANDLED_MESSAGE,
- INVITED: INVITED,
- KICKED: KICKED,
- MEMBERS_JOINED: MEMBERS_JOINED,
- MEMBERS_LEFT: MEMBERS_LEFT,
- MEMBER_INFO_UPDATED: MEMBER_INFO_UPDATED,
- BLOCKED: BLOCKED,
- UNBLOCKED: UNBLOCKED,
- MEMBERS_BLOCKED: MEMBERS_BLOCKED,
- MEMBERS_UNBLOCKED: MEMBERS_UNBLOCKED,
- MUTED: MUTED,
- UNMUTED: UNMUTED,
- MEMBERS_MUTED: MEMBERS_MUTED,
- MEMBERS_UNMUTED: MEMBERS_UNMUTED,
- MESSAGE: MESSAGE$1,
- MESSAGE_RECALL: MESSAGE_RECALL,
- MESSAGE_UPDATE: MESSAGE_UPDATE,
- LAST_DELIVERED_AT_UPDATE: LAST_DELIVERED_AT_UPDATE,
- LAST_READ_AT_UPDATE: LAST_READ_AT_UPDATE,
- INFO_UPDATED: INFO_UPDATED
- });
- /**
- * 消息状态枚举
- * @enum {Symbol}
- * @since 3.2.0
- * @memberof module:leancloud-realtime
- */
- const MessageStatus = {
- /** 初始状态、未知状态 */
- NONE: Symbol('none'),
- /** 正在发送 */
- SENDING: Symbol('sending'),
- /** 已发送 */
- SENT: Symbol('sent'),
- /** 已送达 */
- DELIVERED: Symbol('delivered'),
- /** 发送失败 */
- FAILED: Symbol('failed')
- };
- Object.freeze(MessageStatus);
- const rMessageStatus = {
- [MessageStatus.NONE]: true,
- [MessageStatus.SENDING]: true,
- [MessageStatus.SENT]: true,
- [MessageStatus.DELIVERED]: true,
- [MessageStatus.READ]: true,
- [MessageStatus.FAILED]: true
- };
- class Message {
- /**
- * @implements AVMessage
- * @param {Object|String|ArrayBuffer} content 消息内容
- */
- constructor(content) {
- Object.assign(this, {
- content
- }, {
- /**
- * @type {String}
- * @memberof Message#
- */
- id: uuid(),
- /**
- * 消息所在的 conversation id
- * @memberof Message#
- * @type {String?}
- */
- cid: null,
- /**
- * 消息发送时间
- * @memberof Message#
- * @type {Date}
- */
- timestamp: new Date(),
- /**
- * 消息发送者
- * @memberof Message#
- * @type {String}
- */
- from: undefined,
- /**
- * 消息提及的用户
- * @since 4.0.0
- * @memberof Message#
- * @type {String[]}
- */
- mentionList: [],
- /**
- * 消息是否提及了所有人
- * @since 4.0.0
- * @memberof Message#
- * @type {Boolean}
- */
- mentionedAll: false,
- _mentioned: false
- });
- this._setStatus(MessageStatus.NONE);
- }
- /**
- * 将当前消息的内容序列化为 JSON 对象
- * @private
- * @return {Object}
- */
- getPayload() {
- return this.content;
- }
- _toJSON() {
- const {
- id,
- cid,
- from,
- timestamp,
- deliveredAt,
- updatedAt,
- mentionList,
- mentionedAll,
- mentioned
- } = this;
- return {
- id,
- cid,
- from,
- timestamp,
- deliveredAt,
- updatedAt,
- mentionList,
- mentionedAll,
- mentioned
- };
- }
- /**
- * 返回 JSON 格式的消息
- * @return {Object} 返回值是一个 plain Object
- */
- toJSON() {
- return { ...this._toJSON(),
- data: this.content
- };
- }
- /**
- * 返回 JSON 格式的消息,与 toJSON 不同的是,该对象包含了完整的信息,可以通过 {@link IMClient#parseMessage} 反序列化。
- * @return {Object} 返回值是一个 plain Object
- * @since 4.0.0
- */
- toFullJSON() {
- const {
- content,
- id,
- cid,
- from,
- timestamp,
- deliveredAt,
- _updatedAt,
- mentionList,
- mentionedAll
- } = this;
- return {
- data: content,
- id,
- cid,
- from,
- timestamp: getTime(timestamp),
- deliveredAt: getTime(deliveredAt),
- updatedAt: getTime(_updatedAt),
- mentionList,
- mentionedAll
- };
- }
- /**
- * 消息状态,值为 {@link module:leancloud-realtime.MessageStatus} 之一
- * @type {Symbol}
- * @readonly
- * @since 3.2.0
- */
- get status() {
- return this._status;
- }
- _setStatus(status) {
- if (!rMessageStatus[status]) {
- throw new Error('Invalid message status');
- }
- this._status = status;
- }
- get timestamp() {
- return this._timestamp;
- }
- set timestamp(value) {
- this._timestamp = decodeDate(value);
- }
- /**
- * 消息送达时间
- * @type {?Date}
- */
- get deliveredAt() {
- return this._deliveredAt;
- }
- set deliveredAt(value) {
- this._deliveredAt = decodeDate(value);
- }
- /**
- * 消息修改或撤回时间,可以通过比较其与消息的 timestamp 是否相等判断消息是否被修改过或撤回过。
- * @type {Date}
- * @since 3.5.0
- */
- get updatedAt() {
- return this._updatedAt || this.timestamp;
- }
- set updatedAt(value) {
- this._updatedAt = decodeDate(value);
- }
- /**
- * 当前用户是否在该消息中被提及
- * @type {Boolean}
- * @readonly
- * @since 4.0.0
- */
- get mentioned() {
- return this._mentioned;
- }
- _updateMentioned(client) {
- this._mentioned = this.from !== client && (this.mentionedAll || this.mentionList.indexOf(client) > -1);
- }
- /**
- * 获取提及用户列表
- * @since 4.0.0
- * @return {String[]} 提及用户的 id 列表
- */
- getMentionList() {
- return this.mentionList;
- }
- /**
- * 设置提及用户列表
- * @since 4.0.0
- * @param {String[]} clients 提及用户的 id 列表
- * @return {this} self
- */
- setMentionList(clients) {
- this.mentionList = ensureArray(clients);
- return this;
- }
- /**
- * 设置是否提及所有人
- * @since 4.0.0
- * @param {Boolean} [value=true]
- * @return {this} self
- */
- mentionAll(value = true) {
- this.mentionedAll = Boolean(value);
- return this;
- }
- /**
- * 判断给定的内容是否是有效的 Message,
- * 该方法始终返回 true
- * @private
- * @returns {Boolean}
- * @implements AVMessage.validate
- */
- static validate() {
- return true;
- }
- /**
- * 解析处理消息内容
- * <pre>
- * 如果子类提供了 message,返回该 message
- * 如果没有提供,将 json 作为 content 实例化一个 Message
- * @private
- * @param {Object} json json 格式的消息内容
- * @param {Message} message 子类提供的 message
- * @return {Message}
- * @implements AVMessage.parse
- */
- static parse(json, message) {
- return message || new this(json);
- }
- }
- /* eslint-disable no-param-reassign */
- const messageType = type => {
- if (typeof type !== 'number') {
- throw new TypeError(`${type} is not a Number`);
- }
- return target => {
- target.TYPE = type;
- target.validate = json => json._lctype === type;
- target.prototype._getType = () => ({
- _lctype: type
- });
- };
- }; // documented in ../plugin-im.js
- const messageField = fields => {
- if (typeof fields !== 'string') {
- if (!Array.isArray(fields)) {
- throw new TypeError(`${fields} is not an Array`);
- } else if (fields.some(value => typeof value !== 'string')) {
- throw new TypeError('fields contains non-string typed member');
- }
- }
- return target => {
- // IE10 Hack:
- // static properties in IE10 will not be inherited from super
- // search for parse method and assign it manually
- let originalCustomFields = isIE10 ? getStaticProperty(target, '_customFields') : target._customFields;
- originalCustomFields = Array.isArray(originalCustomFields) ? originalCustomFields : [];
- target._customFields = originalCustomFields.concat(fields);
- };
- }; // IE10 Hack:
- // static properties in IE10 will not be inherited from super
- // search for parse method and assign it manually
- const IE10Compatible = target => {
- if (isIE10) {
- target.parse = getStaticProperty(target, 'parse');
- }
- };
- var _dec, _class$1;
- let // jsdoc-ignore-end
- /**
- * 所有内置的富媒体消息均继承自本类
- * @extends Message
- */
- TypedMessage = (_dec = messageField(['_lctext', '_lcattrs']), _dec(_class$1 = class TypedMessage extends Message {
- /**
- * @type {Number}
- * @readonly
- */
- get type() {
- return this.constructor.TYPE;
- }
- /** @type {String} */
- set text(text) {
- return this.setText(text);
- }
- get text() {
- return this.getText();
- }
- /** @type {Object} */
- set attributes(attributes) {
- return this.setAttributes(attributes);
- }
- get attributes() {
- return this.getAttributes();
- }
- /**
- * 在客户端需要以文本形式展示该消息时显示的文案,
- * 如 <code>[红包] 新春快乐</code>。
- * 默认值为消息的 text。
- * @type {String}
- * @readonly
- */
- get summary() {
- return this.text;
- }
- /**
- * @param {String} text
- * @return {this} self
- */
- setText(text) {
- this._lctext = text;
- return this;
- }
- /**
- * @return {String}
- */
- getText() {
- return this._lctext;
- }
- /**
- * @param {Object} attributes
- * @return {this} self
- */
- setAttributes(attributes) {
- this._lcattrs = attributes;
- return this;
- }
- /**
- * @return {Object}
- */
- getAttributes() {
- return this._lcattrs;
- }
- _getCustomFields() {
- const fields = Array.isArray(this.constructor._customFields) ? this.constructor._customFields : [];
- return fields.reduce((result, field) => {
- if (typeof field !== 'string') return result;
- result[field] = this[field]; // eslint-disable-line no-param-reassign
- return result;
- }, {});
- }
- /* eslint-disable class-methods-use-this */
- _getType() {
- throw new Error('not implemented');
- }
- /* eslint-enable class-methods-use-this */
- getPayload() {
- return compact({
- _lctext: this.getText(),
- _lcattrs: this.getAttributes(),
- ...this._getCustomFields(),
- ...this._getType()
- });
- }
- toJSON() {
- const {
- type,
- text,
- attributes,
- summary
- } = this;
- return { ...super._toJSON(),
- type,
- text,
- attributes,
- summary
- };
- }
- toFullJSON() {
- return { ...super.toFullJSON(),
- data: this.getPayload()
- };
- }
- /**
- * 解析处理消息内容
- * <pre>
- * 为给定的 message 设置 text 与 attributes 属性,返回该 message
- * 如果子类没有提供 message,new this()
- * @protected
- * @param {Object} json json 格式的消息内容
- * @param {TypedMessage} message 子类提供的 message
- * @return {TypedMessage}
- * @implements AVMessage.parse
- */
- static parse(json, message = new this()) {
- message.content = json; // eslint-disable-line no-param-reassign
- const customFields = isIE10 ? getStaticProperty(message.constructor, '_customFields') : message.constructor._customFields;
- let fields = Array.isArray(customFields) ? customFields : [];
- fields = fields.reduce((result, field) => {
- if (typeof field !== 'string') return result;
- result[field] = json[field]; // eslint-disable-line no-param-reassign
- return result;
- }, {});
- Object.assign(message, fields);
- return super.parse(json, message);
- }
- }) || _class$1);
- var _dec$1, _class$2;
- let // jsdoc-ignore-end
- /**
- * 已撤回类型消息,当消息被撤回时,SDK 会使用该类型的消息替代原始消息
- * @extends TypedMessage
- */
- RecalledMessage = (_dec$1 = messageType(-127), _dec$1(_class$2 = IE10Compatible(_class$2 = class RecalledMessage extends TypedMessage {
- /**
- * 在客户端需要以文本形式展示该消息时显示的文案,值为 <code>[该消息已撤回]</code>
- * @type {String}
- * @readonly
- */
- // eslint-disable-next-line class-methods-use-this
- get summary() {
- return '[该消息已撤回]';
- }
- }) || _class$2) || _class$2);
- /* eslint class-methods-use-this: ["error", { "exceptMethods": ["_addMembers", "_removeMembers"] }] */
- const debug$7 = d('LC:Conversation');
- const serializeMessage = message => {
- const content = message.getPayload();
- let msg;
- let binaryMsg;
- if (content instanceof ArrayBuffer) {
- binaryMsg = content;
- } else if (typeof content !== 'string') {
- msg = JSON.stringify(content);
- } else {
- msg = content;
- }
- return {
- msg,
- binaryMsg
- };
- };
- const {
- NEW,
- OLD
- } = LogsCommand.QueryDirection;
- /**
- * 历史消息查询方向枚举
- * @enum {Number}
- * @since 4.0.0
- * @memberof module:leancloud-realtime
- */
- const MessageQueryDirection = {
- /** 从后向前 */
- NEW_TO_OLD: OLD,
- /** 从前向后 */
- OLD_TO_NEW: NEW
- };
- Object.freeze(MessageQueryDirection);
- class ConversationBase extends EventEmitter {
- /**
- * @extends EventEmitter
- * @private
- * @abstract
- */
- constructor({
- id,
- lastMessageAt,
- lastMessage,
- lastDeliveredAt,
- lastReadAt,
- unreadMessagesCount = 0,
- members = [],
- mentioned = false,
- ...properties
- }, client) {
- super();
- Object.assign(this, {
- /**
- * 对话 id,对应 _Conversation 表中的 objectId
- * @memberof ConversationBase#
- * @type {String}
- */
- id,
- /**
- * 最后一条消息时间
- * @memberof ConversationBase#
- * @type {?Date}
- */
- lastMessageAt,
- /**
- * 最后一条消息
- * @memberof ConversationBase#
- * @type {?Message}
- */
- lastMessage,
- /**
- * 参与该对话的用户列表
- * @memberof ConversationBase#
- * @type {String[]}
- */
- members,
- // other properties provided by subclasses
- ...properties
- });
- this.members = Array.from(new Set(this.members));
- Object.assign(internal(this), {
- messagesWaitingForReceipt: {},
- lastDeliveredAt,
- lastReadAt,
- unreadMessagesCount,
- mentioned
- });
- this._client = client;
- if (debug$7.enabled) {
- values(IMEvent).forEach(event => this.on(event, (...payload) => this._debug(`${event} event emitted. %o`, payload)));
- } // onConversationCreate hook
- applyDecorators(this._client._plugins.onConversationCreate, this);
- }
- /**
- * 当前用户是否在该对话的未读消息中被提及
- * @type {Boolean}
- * @since 4.0.0
- */
- get unreadMessagesMentioned() {
- return internal(this).unreadMessagesMentioned;
- }
- _setUnreadMessagesMentioned(value) {
- internal(this).unreadMessagesMentioned = Boolean(value);
- }
- set unreadMessagesCount(value) {
- if (value !== this.unreadMessagesCount) {
- internal(this).unreadMessagesCount = value;
- this._client.emit(UNREAD_MESSAGES_COUNT_UPDATE, [this]);
- }
- }
- /**
- * 当前用户在该对话的未读消息数
- * @type {Number}
- */
- get unreadMessagesCount() {
- return internal(this).unreadMessagesCount;
- }
- set lastMessageAt(value) {
- const time = decodeDate(value);
- if (time <= this._lastMessageAt) return;
- this._lastMessageAt = time;
- }
- get lastMessageAt() {
- return this._lastMessageAt;
- }
- /**
- * 最后消息送达时间,常用来实现消息的「已送达」标记,可通过 {@link Conversation#fetchReceiptTimestamps} 获取或更新该属性
- * @type {?Date}
- * @since 3.4.0
- */
- get lastDeliveredAt() {
- if (this.members.length !== 2) return null;
- return internal(this).lastDeliveredAt;
- }
- _setLastDeliveredAt(value) {
- const date = decodeDate(value);
- if (!(date < internal(this).lastDeliveredAt)) {
- internal(this).lastDeliveredAt = date;
- /**
- * 最后消息送达时间更新
- * @event ConversationBase#LAST_DELIVERED_AT_UPDATE
- * @since 3.4.0
- */
- this.emit(LAST_DELIVERED_AT_UPDATE);
- }
- }
- /**
- * 最后消息被阅读时间,常用来实现发送消息的「已读」标记,可通过 {@link Conversation#fetchReceiptTimestamps} 获取或更新该属性
- * @type {?Date}
- * @since 3.4.0
- */
- get lastReadAt() {
- if (this.members.length !== 2) return null;
- return internal(this).lastReadAt;
- }
- _setLastReadAt(value) {
- const date = decodeDate(value);
- if (!(date < internal(this).lastReadAt)) {
- internal(this).lastReadAt = date;
- /**
- * 最后消息被阅读时间更新
- * @event ConversationBase#LAST_READ_AT_UPDATE
- * @since 3.4.0
- */
- this.emit(LAST_READ_AT_UPDATE);
- }
- }
- /**
- * 返回 JSON 格式的对话,与 toJSON 不同的是,该对象包含了完整的信息,可以通过 {@link IMClient#parseConversation} 反序列化。
- * @return {Object} 返回值是一个 plain Object
- * @since 4.0.0
- */
- toFullJSON() {
- const {
- id,
- members,
- lastMessageAt,
- lastDeliveredAt,
- lastReadAt,
- lastMessage,
- unreadMessagesCount
- } = this;
- return {
- id,
- members,
- lastMessageAt: getTime(lastMessageAt),
- lastDeliveredAt: getTime(lastDeliveredAt),
- lastReadAt: getTime(lastReadAt),
- lastMessage: lastMessage ? lastMessage.toFullJSON() : undefined,
- unreadMessagesCount
- };
- }
- /**
- * 返回 JSON 格式的对话
- * @return {Object} 返回值是一个 plain Object
- * @since 4.0.0
- */
- toJSON() {
- const {
- id,
- members,
- lastMessageAt,
- lastDeliveredAt,
- lastReadAt,
- lastMessage,
- unreadMessagesCount,
- unreadMessagesMentioned
- } = this;
- return {
- id,
- members,
- lastMessageAt,
- lastDeliveredAt,
- lastReadAt,
- lastMessage: lastMessage ? lastMessage.toJSON() : undefined,
- unreadMessagesCount,
- unreadMessagesMentioned
- };
- }
- _debug(...params) {
- debug$7(...params, `[${this.id}]`);
- }
- _send(command, ...args) {
- /* eslint-disable no-param-reassign */
- if (command.cmd === null) {
- command.cmd = 'conv';
- }
- if (command.cmd === 'conv' && command.convMessage === null) {
- command.convMessage = new ConvCommand();
- }
- if (command.convMessage && command.convMessage.cid === null) {
- command.convMessage.cid = this.id;
- }
- /* eslint-enable no-param-reassign */
- return this._client._send(command, ...args);
- }
- /**
- * 发送消息
- * @param {Message} message 消息,Message 及其子类的实例
- * @param {Object} [options] since v3.3.0,发送选项
- * @param {Boolean} [options.transient] since v3.3.1,是否作为暂态消息发送
- * @param {Boolean} [options.receipt] 是否需要回执,仅在普通对话中有效
- * @param {Boolean} [options.will] since v3.4.0,是否指定该消息作为「掉线消息」发送,
- * 「掉线消息」会延迟到当前用户掉线后发送,常用来实现「下线通知」功能
- * @param {MessagePriority} [options.priority] 消息优先级,仅在暂态对话中有效,
- * see: {@link module:leancloud-realtime.MessagePriority MessagePriority}
- * @param {Object} [options.pushData] 消息对应的离线推送内容,如果消息接收方不在线,会推送指定的内容。其结构说明参见: {@link https://url.leanapp.cn/pushData 推送消息内容}
- * @return {Promise.<Message>} 发送的消息
- */
- async send(message, options) {
- this._debug(message, 'send');
- if (!(message instanceof Message)) {
- throw new TypeError(`${message} is not a Message`);
- }
- const {
- transient,
- receipt,
- priority,
- pushData,
- will
- } = { // support Message static property: sendOptions
- ...message.constructor.sendOptions,
- // support Message static property: getSendOptions
- ...(typeof message.constructor.getSendOptions === 'function' ? message.constructor.getSendOptions(message) : {}),
- ...options
- };
- if (receipt) {
- if (this.transient) {
- console.warn('receipt option is ignored as the conversation is transient.');
- } else if (transient) {
- console.warn('receipt option is ignored as the message is sent transiently.');
- } else if (this.members.length > 2) {
- console.warn('receipt option is recommended to be used in one-on-one conversation.'); // eslint-disable-line max-len
- }
- }
- if (priority && !this.transient) {
- console.warn('priority option is ignored as the conversation is not transient.');
- }
- Object.assign(message, {
- cid: this.id,
- from: this._client.id
- });
- message._setStatus(MessageStatus.SENDING);
- const {
- msg,
- binaryMsg
- } = serializeMessage(message);
- const command = new GenericCommand({
- cmd: 'direct',
- directMessage: new DirectCommand({
- msg,
- binaryMsg,
- cid: this.id,
- r: receipt,
- transient,
- dt: message.id,
- pushData: JSON.stringify(pushData),
- will,
- mentionPids: message.mentionList,
- mentionAll: message.mentionedAll
- }),
- priority
- });
- try {
- const resCommand = await this._send(command);
- const {
- ackMessage: {
- uid,
- t,
- code,
- reason,
- appCode
- }
- } = resCommand;
- if (code !== null) {
- throw createError({
- code,
- reason,
- appCode
- });
- }
- Object.assign(message, {
- id: uid,
- timestamp: t
- });
- if (!transient) {
- this.lastMessage = message;
- this.lastMessageAt = message.timestamp;
- }
- message._setStatus(MessageStatus.SENT);
- if (receipt) {
- internal(this).messagesWaitingForReceipt[message.id] = message;
- }
- return message;
- } catch (error) {
- message._setStatus(MessageStatus.FAILED);
- throw error;
- }
- }
- async _update(message, newMessage, recall) {
- this._debug('patch %O %O %O', message, newMessage, recall);
- if (message instanceof Message) {
- if (message.from !== this._client.id) {
- throw new Error('Updating message from others is not allowed');
- }
- if (message.status !== MessageStatus.SENT && message.status !== MessageStatus.DELIVERED) {
- throw new Error('Message is not sent');
- }
- } else if (!(message.id && message.timestamp)) {
- throw new TypeError(`${message} is not a Message`);
- }
- let msg;
- let binaryMsg;
- if (!recall) {
- const content = serializeMessage(newMessage);
- ({
- msg,
- binaryMsg
- } = content);
- }
- await this._send(new GenericCommand({
- cmd: CommandType.patch,
- op: OpType.modify,
- patchMessage: new PatchCommand({
- patches: [new PatchItem({
- cid: this.id,
- mid: message.id,
- timestamp: Number(message.timestamp),
- recall,
- data: msg,
- binaryMsg,
- mentionPids: newMessage.mentionList,
- mentionAll: newMessage.mentionedAll
- })],
- lastPatchTime: this._client._lastPatchTime
- })
- }));
- const {
- id,
- cid,
- timestamp,
- from,
- _status
- } = message;
- Object.assign(newMessage, {
- id,
- cid,
- timestamp,
- from,
- _status
- });
- if (this.lastMessage && this.lastMessage.id === newMessage.id) {
- this.lastMessage = newMessage;
- }
- return newMessage;
- }
- /**
- * 获取对话人数,或暂态对话的在线人数
- * @return {Promise.<Number>}
- */
- async count() {
- this._debug('count');
- const resCommand = await this._send(new GenericCommand({
- op: 'count'
- }));
- return resCommand.convMessage.count;
- }
- /**
- * 应用增加成员的操作,产生副作用
- * @param {string[]} members
- * @abstract
- * @private
- */
- _addMembers() {}
- /**
- * 应用减少成员的操作,产生副作用
- * @param {string[]} members
- * @abstract
- * @private
- */
- _removeMembers() {}
- /**
- * 修改已发送的消息
- * @param {AVMessage} message 要修改的消息,该消息必须是由当前用户发送的。也可以提供一个包含消息 {id, timestamp} 的对象
- * @param {AVMessage} newMessage 新的消息
- * @return {Promise.<AVMessage>} 更新后的消息
- */
- async update(message, newMessage) {
- if (!(newMessage instanceof Message)) {
- throw new TypeError(`${newMessage} is not a Message`);
- }
- return this._update(message, newMessage, false);
- }
- /**
- * 撤回已发送的消息
- * @param {AVMessage} message 要撤回的消息,该消息必须是由当前用户发送的。也可以提供一个包含消息 {id, timestamp} 的对象
- * @return {Promise.<RecalledMessage>} 一条已撤回的消息
- */
- async recall(message) {
- return this._update(message, new RecalledMessage(), true);
- }
- /**
- * 查询消息记录
- * 如果仅需实现消息向前记录翻页查询需求,建议使用 {@link Conversation#createMessagesIterator}。
- * 不论何种方向,获得的消息都是按照时间升序排列的。
- * startClosed 与 endClosed 用于指定查询区间的开闭。
- *
- * @param {Object} [options]
- * @param {Number} [options.limit] 限制查询结果的数量,目前服务端默认为 20
- * @param {Number} [options.type] 指定查询的富媒体消息类型,不指定则查询所有消息。
- * @param {MessageQueryDirection} [options.direction] 查询的方向。
- * 在不指定的情况下如果 startTime 大于 endTime,则为从新到旧查询,可以实现加载聊天记录等场景。
- * 如果 startTime 小于 endTime,则为从旧到新查询,可以实现弹幕等场景。
- * @param {Date} [options.startTime] 从该时间开始查询,不传则从当前时间开始查询
- * @param {String} [options.startMessageId] 从该消息之前开始查询,需要与 startTime 同时使用,为防止某时刻有重复消息
- * @param {Boolean}[options.startClosed] 指定查询范围是否包括开始的时间点,默认不包括
- * @param {Date} [options.endTime] 查询到该时间为止,不传则查询最早消息为止
- * @param {String} [options.endMessageId] 查询到该消息为止,需要与 endTime 同时使用,为防止某时刻有重复消息
- * @param {Boolean}[options.endClosed] 指定查询范围是否包括结束的时间点,默认不包括
- *
- * @param {Date} [options.beforeTime] DEPRECATED: 使用 startTime 代替。限制查询结果为小于该时间之前的消息,不传则为当前时间
- * @param {String} [options.beforeMessageId] DEPRECATED: 使用 startMessageId 代替。
- * 限制查询结果为该消息之前的消息,需要与 beforeTime 同时使用,为防止某时刻有重复消息
- * @param {Date} [options.afterTime] DEPRECATED: 使用 endTime 代替。限制查询结果为大于该时间之前的消息
- * @param {String} [options.afterMessageId] DEPRECATED: 使用 endMessageId 代替。
- * 限制查询结果为该消息之后的消息,需要与 afterTime 同时使用,为防止某时刻有重复消息
- * @return {Promise.<Message[]>} 消息列表
- */
- async queryMessages(options = {}) {
- this._debug('query messages %O', options);
- const {
- beforeTime,
- beforeMessageId,
- afterTime,
- afterMessageId,
- limit,
- direction,
- type,
- startTime,
- startMessageId,
- startClosed,
- endTime,
- endMessageId,
- endClosed
- } = options;
- if (beforeMessageId || beforeTime || afterMessageId || afterTime) {
- console.warn('DEPRECATION: queryMessages options beforeTime, beforeMessageId, afterTime and afterMessageId are deprecated in favor of startTime, startMessageId, endTime and endMessageId.');
- return this.queryMessages({
- startTime: beforeTime,
- startMessageId: beforeMessageId,
- endTime: afterTime,
- endMessageId: afterMessageId,
- limit
- });
- }
- if (startMessageId && !startTime) {
- throw new Error('query option startMessageId must be used with option startTime');
- }
- if (endMessageId && !endTime) {
- throw new Error('query option endMessageId must be used with option endTime');
- }
- const conditions = {
- t: startTime,
- mid: startMessageId,
- tIncluded: startClosed,
- tt: endTime,
- tmid: endMessageId,
- ttIncluded: endClosed,
- l: limit,
- lctype: type
- };
- if (conditions.t instanceof Date) {
- conditions.t = conditions.t.getTime();
- }
- if (conditions.tt instanceof Date) {
- conditions.tt = conditions.tt.getTime();
- }
- if (direction !== undefined) {
- conditions.direction = direction;
- } else if (conditions.tt > conditions.t) {
- conditions.direction = MessageQueryDirection.OLD_TO_NEW;
- }
- const resCommand = await this._send(new GenericCommand({
- cmd: 'logs',
- logsMessage: new LogsCommand(Object.assign(conditions, {
- cid: this.id
- }))
- }));
- return Promise.all(resCommand.logsMessage.logs.map(async ({
- msgId,
- timestamp,
- patchTimestamp,
- from,
- ackAt,
- readAt,
- data,
- mentionAll,
- mentionPids,
- bin
- }) => {
- const messageData = {
- data,
- bin,
- id: msgId,
- cid: this.id,
- timestamp,
- from,
- deliveredAt: ackAt,
- updatedAt: patchTimestamp,
- mentionList: mentionPids,
- mentionedAll: mentionAll
- };
- const message = await this._client.parseMessage(messageData);
- let status = MessageStatus.SENT;
- if (this.members.length === 2) {
- if (ackAt) status = MessageStatus.DELIVERED;
- if (ackAt) this._setLastDeliveredAt(ackAt);
- if (readAt) this._setLastReadAt(readAt);
- }
- message._setStatus(status);
- return message;
- }));
- }
- /**
- * 获取消息翻页迭代器
- * @param {Object} [options]
- * @param {Date} [options.beforeTime] 限制起始查询结果为小于该时间之前的消息,不传则为当前时间
- * @param {String} [options.beforeMessageId] 限制起始查询结果为该消息之前的消息,需要与 beforeTime 同时使用,为防止某时刻有重复消息
- * @param {Number} [options.limit] 限制每页查询结果的数量,目前服务端默认为 20
- * @return {AsyncIterater.<Promise.<IteratorResult<Message[]>>>} [AsyncIterator]{@link https://github.com/tc39/proposal-async-iteration},调用其 next 方法返回获取下一页消息的 Promise
- * @example
- * var messageIterator = conversation.createMessagesIterator({ limit: 10 });
- * messageIterator.next().then(function(result) {
- * // result: {
- * // value: [message1, ..., message10],
- * // done: false,
- * // }
- * });
- * messageIterator.next().then(function(result) {
- * // result: {
- * // value: [message11, ..., message20],
- * // done: false,
- * // }
- * });
- * messageIterator.next().then(function(result) {
- * // No more messages
- * // result: { value: [], done: true }
- * });
- */
- createMessagesIterator({
- beforeTime,
- beforeMessageId,
- limit
- } = {}) {
- let promise;
- return {
- next: () => {
- if (promise === undefined) {
- // first call
- promise = this.queryMessages({
- limit,
- startTime: beforeTime,
- startMessageId: beforeMessageId
- });
- } else {
- promise = promise.then(prevMessages => {
- if (prevMessages.length === 0 || prevMessages.length < limit) {
- // no more messages
- return [];
- }
- return this.queryMessages({
- startTime: prevMessages[0].timestamp,
- startMessageId: prevMessages[0].id,
- limit
- });
- });
- }
- return promise.then(value => ({
- value: Array.from(value),
- done: value.length === 0 || value.length < limit
- }));
- }
- };
- }
- /**
- * 将该会话标记为已读
- * @return {Promise.<this>} self
- */
- async read() {
- this.unreadMessagesCount = 0;
- this._setUnreadMessagesMentioned(false); // 跳过暂态会话
- if (this.transient) return this;
- const client = this._client;
- if (!internal(client).readConversationsBuffer) {
- internal(client).readConversationsBuffer = new Set();
- }
- internal(client).readConversationsBuffer.add(this);
- client._doSendRead();
- return this;
- }
- _handleReceipt({
- messageId,
- timestamp,
- read
- }) {
- if (read) {
- this._setLastReadAt(timestamp);
- } else {
- this._setLastDeliveredAt(timestamp);
- }
- const {
- messagesWaitingForReceipt
- } = internal(this);
- const message = messagesWaitingForReceipt[messageId];
- if (!message) return;
- message._setStatus(MessageStatus.DELIVERED);
- message.deliveredAt = timestamp;
- delete messagesWaitingForReceipt[messageId];
- }
- /**
- * 更新对话的最新回执时间戳(lastDeliveredAt、lastReadAt)
- * @since 3.4.0
- * @return {Promise.<this>} this
- */
- async fetchReceiptTimestamps() {
- // 暂态/系统会话不支持回执
- if (this.transient || this.system) return this;
- const {
- convMessage: {
- maxReadTimestamp,
- maxAckTimestamp
- }
- } = await this._send(new GenericCommand({
- op: 'max_read'
- }));
- this._setLastDeliveredAt(maxAckTimestamp);
- this._setLastReadAt(maxReadTimestamp);
- return this;
- }
- _fetchAllReceiptTimestamps() {
- // 暂态/系统会话不支持回执
- if (this.transient || this.system) return this;
- const convMessage = new ConvCommand({
- queryAllMembers: true
- });
- return this._send(new GenericCommand({
- op: 'max_read',
- convMessage
- })).then(({
- convMessage: {
- maxReadTuples
- }
- }) => maxReadTuples.filter(maxReadTuple => maxReadTuple.maxAckTimestamp || maxReadTuple.maxReadTimestamp).map(({
- pid,
- maxAckTimestamp,
- maxReadTimestamp
- }) => ({
- pid,
- lastDeliveredAt: decodeDate(maxAckTimestamp),
- lastReadAt: decodeDate(maxReadTimestamp)
- })));
- }
- }
- const debug$8 = d('LC:SignatureFactoryRunner');
- function _validateSignature(signatureResult = {}) {
- const {
- signature,
- timestamp,
- nonce
- } = signatureResult;
- if (typeof signature !== 'string' || typeof timestamp !== 'number' || typeof nonce !== 'string') {
- throw new Error('malformed signature');
- }
- return {
- signature,
- timestamp,
- nonce
- };
- }
- var runSignatureFactory = ((signatureFactory, params) => Promise.resolve().then(() => {
- debug$8('call signatureFactory with %O', params);
- return signatureFactory(...params);
- }).then(tap(signatureResult => debug$8('sign result %O', signatureResult)), error => {
- // eslint-disable-next-line no-param-reassign
- error.message = `sign error: ${error.message}`;
- debug$8(error);
- throw error;
- }).then(_validateSignature));
- /**
- * 部分失败异常
- * @typedef OperationFailureError
- * @type {Error}
- * @property {string} message 异常信息
- * @property {string[]} clientIds 因为该原因失败的 client id 列表
- * @property {number} [code] 错误码
- * @property {string} [detail] 详细信息
- */
- /**
- * 部分成功的结果
- * @typedef PartiallySuccess
- * @type {Object}
- * @property {string[]} successfulClientIds 成功的 client id 列表
- * @property {OperationFailureError[]} failures 失败的异常列表
- */
- /**
- * 分页查询结果
- * @typedef PagedResults
- * @type {Object}
- * @property {T[]} results 查询结果
- * @property {string} [next] 存在表示还有更多结果,在下次查询中带上可实现翻页。
- */
- const createPartiallySuccess = ({
- allowedPids,
- failedPids
- }) => ({
- successfulClientIds: allowedPids,
- failures: failedPids.map(({
- pids,
- ...error
- }) => Object.assign(createError(error), {
- clientIds: pids
- }))
- });
- /**
- * @extends ConversationBase
- * @private
- * @abstract
- */
- class PersistentConversation extends ConversationBase {
- constructor(data, {
- creator,
- createdAt,
- updatedAt,
- transient = false,
- system = false,
- muted = false,
- mutedMembers = [],
- ...attributes
- }, client) {
- super({ ...data,
- /**
- * 对话创建者
- * @memberof PersistentConversation#
- * @type {String}
- */
- creator,
- /**
- * 对话创建时间
- * @memberof PersistentConversation#
- * @type {Date}
- */
- createdAt,
- /**
- * 对话更新时间
- * @memberof PersistentConversation#
- * @type {Date}
- */
- updatedAt,
- /**
- * 对该对话设置了静音的用户列表
- * @memberof PersistentConversation#
- * @type {?String[]}
- */
- mutedMembers,
- /**
- * 暂态对话标记
- * @memberof PersistentConversation#
- * @type {Boolean}
- */
- transient,
- /**
- * 系统对话标记
- * @memberof PersistentConversation#
- * @type {Boolean}
- * @since 3.3.0
- */
- system,
- /**
- * 当前用户静音该对话标记
- * @memberof PersistentConversation#
- * @type {Boolean}
- */
- muted,
- _attributes: attributes
- }, client);
- this._reset();
- }
- set createdAt(value) {
- this._createdAt = decodeDate(value);
- }
- get createdAt() {
- return this._createdAt;
- }
- set updatedAt(value) {
- this._updatedAt = decodeDate(value);
- }
- get updatedAt() {
- return this._updatedAt;
- }
- /**
- * 对话名字,对应 _Conversation 表中的 name
- * @type {String}
- */
- get name() {
- return this.get('name');
- }
- set name(value) {
- this.set('name', value);
- }
- /**
- * 获取对话的自定义属性
- * @since 3.2.0
- * @param {String} key key 属性的键名,'x' 对应 Conversation 表中的 x 列
- * @return {Any} 属性的值
- */
- get(key) {
- return get(internal(this).currentAttributes, key);
- }
- /**
- * 设置对话的自定义属性
- * @since 3.2.0
- * @param {String} key 属性的键名,'x' 对应 Conversation 表中的 x 列,支持使用 'x.y.z' 来修改对象的部分字段。
- * @param {Any} value 属性的值
- * @return {this} self
- * @example
- *
- * // 设置对话的 color 属性
- * conversation.set('color', {
- * text: '#000',
- * background: '#DDD',
- * });
- * // 设置对话的 color.text 属性
- * conversation.set('color.text', '#333');
- */
- set(key, value) {
- this._debug(`set [${key}]: ${value}`);
- const {
- pendingAttributes
- } = internal(this);
- const pendingKeys = Object.keys(pendingAttributes); // suppose pendingAttributes = { 'a.b': {} }
- // set 'a' or 'a.b': delete 'a.b'
- const re = new RegExp(`^${key}`);
- const childKeys = pendingKeys.filter(re.test.bind(re));
- childKeys.forEach(k => {
- delete pendingAttributes[k];
- });
- if (childKeys.length) {
- pendingAttributes[key] = value;
- } else {
- // set 'a.c': nothing to do
- // set 'a.b.c.d': assign c: { d: {} } to 'a.b'
- const parentKey = find(pendingKeys, k => key.indexOf(k) === 0); // 'a.b'
- if (parentKey) {
- setValue(pendingAttributes[parentKey], key.slice(parentKey.length + 1), value);
- } else {
- pendingAttributes[key] = value;
- }
- }
- this._buildCurrentAttributes();
- return this;
- }
- _buildCurrentAttributes() {
- const {
- pendingAttributes
- } = internal(this);
- internal(this).currentAttributes = Object.keys(pendingAttributes).reduce((target, k) => setValue(target, k, pendingAttributes[k]), cloneDeep(this._attributes));
- }
- _updateServerAttributes(attributes) {
- Object.keys(attributes).forEach(key => setValue(this._attributes, key, attributes[key]));
- this._buildCurrentAttributes();
- }
- _reset() {
- Object.assign(internal(this), {
- pendingAttributes: {},
- currentAttributes: this._attributes
- });
- }
- /**
- * 保存当前对话的属性至服务器
- * @return {Promise.<this>} self
- */
- async save() {
- this._debug('save');
- const attr = internal(this).pendingAttributes;
- if (isEmpty(attr)) {
- this._debug('nothing touched, resolve with self');
- return this;
- }
- this._debug('attr: %O', attr);
- const convMessage = new ConvCommand({
- attr: new JsonObjectMessage({
- data: JSON.stringify(encode(attr))
- })
- });
- const resCommand = await this._send(new GenericCommand({
- op: 'update',
- convMessage
- }));
- this.updatedAt = resCommand.convMessage.udate;
- this._attributes = internal(this).currentAttributes;
- internal(this).pendingAttributes = {};
- return this;
- }
- /**
- * 从服务器更新对话的属性
- * @return {Promise.<this>} self
- */
- async fetch() {
- const query = this._client.getQuery().equalTo('objectId', this.id);
- await query.find();
- return this;
- }
- /**
- * 静音,客户端拒绝收到服务器端的离线推送通知
- * @return {Promise.<this>} self
- */
- async mute() {
- this._debug('mute');
- await this._send(new GenericCommand({
- op: 'mute'
- }));
- if (!this.transient) {
- this.muted = true;
- this.mutedMembers = union(this.mutedMembers, [this._client.id]);
- }
- return this;
- }
- /**
- * 取消静音
- * @return {Promise.<this>} self
- */
- async unmute() {
- this._debug('unmute');
- await this._send(new GenericCommand({
- op: 'unmute'
- }));
- if (!this.transient) {
- this.muted = false;
- this.mutedMembers = difference(this.mutedMembers, [this._client.id]);
- }
- return this;
- }
- async _appendConversationSignature(command, action, clientIds) {
- if (this._client.options.conversationSignatureFactory) {
- const params = [this.id, this._client.id, clientIds.sort(), action];
- const signatureResult = await runSignatureFactory(this._client.options.conversationSignatureFactory, params);
- Object.assign(command.convMessage, keyRemap({
- signature: 's',
- timestamp: 't',
- nonce: 'n'
- }, signatureResult));
- }
- }
- async _appendBlacklistSignature(command, action, clientIds) {
- if (this._client.options.blacklistSignatureFactory) {
- const params = [this.id, this._client.id, clientIds.sort(), action];
- const signatureResult = await runSignatureFactory(this._client.options.blacklistSignatureFactory, params);
- Object.assign(command.blacklistMessage, keyRemap({
- signature: 's',
- timestamp: 't',
- nonce: 'n'
- }, signatureResult));
- }
- }
- /**
- * 增加成员
- * @param {String|String[]} clientIds 新增成员 client id
- * @return {Promise.<PartiallySuccess>} 部分成功结果,包含了成功的 id 列表、失败原因与对应的 id 列表
- */
- async add(clientIds) {
- this._debug('add', clientIds);
- if (typeof clientIds === 'string') {
- clientIds = [clientIds]; // eslint-disable-line no-param-reassign
- }
- const command = new GenericCommand({
- op: 'add',
- convMessage: new ConvCommand({
- m: clientIds
- })
- });
- await this._appendConversationSignature(command, 'invite', clientIds);
- const {
- convMessage,
- convMessage: {
- allowedPids
- }
- } = await this._send(command);
- this._addMembers(allowedPids);
- return createPartiallySuccess(convMessage);
- }
- /**
- * 剔除成员
- * @param {String|String[]} clientIds 成员 client id
- * @return {Promise.<PartiallySuccess>} 部分成功结果,包含了成功的 id 列表、失败原因与对应的 id 列表
- */
- async remove(clientIds) {
- this._debug('remove', clientIds);
- if (typeof clientIds === 'string') {
- clientIds = [clientIds]; // eslint-disable-line no-param-reassign
- }
- const command = new GenericCommand({
- op: 'remove',
- convMessage: new ConvCommand({
- m: clientIds
- })
- });
- await this._appendConversationSignature(command, 'kick', clientIds);
- const {
- convMessage,
- convMessage: {
- allowedPids
- }
- } = await this._send(command);
- this._removeMembers(allowedPids);
- return createPartiallySuccess(convMessage);
- }
- /**
- * (当前用户)加入该对话
- * @return {Promise.<this>} self
- */
- async join() {
- this._debug('join');
- return this.add(this._client.id).then(({
- failures
- }) => {
- if (failures[0]) throw failures[0];
- return this;
- });
- }
- /**
- * (当前用户)退出该对话
- * @return {Promise.<this>} self
- */
- async quit() {
- this._debug('quit');
- return this.remove(this._client.id).then(({
- failures
- }) => {
- if (failures[0]) throw failures[0];
- return this;
- });
- }
- /**
- * 在该对话中禁言成员
- * @param {String|String[]} clientIds 成员 client id
- * @return {Promise.<PartiallySuccess>} 部分成功结果,包含了成功的 id 列表、失败原因与对应的 id 列表
- */
- async muteMembers(clientIds) {
- this._debug('mute', clientIds);
- clientIds = ensureArray(clientIds); // eslint-disable-line no-param-reassign
- const command = new GenericCommand({
- op: OpType.add_shutup,
- convMessage: new ConvCommand({
- m: clientIds
- })
- });
- const {
- convMessage
- } = await this._send(command);
- return createPartiallySuccess(convMessage);
- }
- /**
- * 在该对话中解除成员禁言
- * @param {String|String[]} clientIds 成员 client id
- * @return {Promise.<PartiallySuccess>} 部分成功结果,包含了成功的 id 列表、失败原因与对应的 id 列表
- */
- async unmuteMembers(clientIds) {
- this._debug('unmute', clientIds);
- clientIds = ensureArray(clientIds); // eslint-disable-line no-param-reassign
- const command = new GenericCommand({
- op: OpType.remove_shutup,
- convMessage: new ConvCommand({
- m: clientIds
- })
- });
- const {
- convMessage
- } = await this._send(command);
- return createPartiallySuccess(convMessage);
- }
- /**
- * 查询该对话禁言成员列表
- * @param {Object} [options]
- * @param {Number} [options.limit] 返回的成员数量,服务器默认值 10
- * @param {String} [options.next] 从指定 next 开始查询,与 limit 一起使用可以完成翻页。
- * @return {PagedResults.<string>} 查询结果。其中的 cureser 存在表示还有更多结果。
- */
- async queryMutedMembers({
- limit,
- next
- } = {}) {
- this._debug('query muted: limit %O, next: %O', limit, next);
- const command = new GenericCommand({
- op: OpType.query_shutup,
- convMessage: new ConvCommand({
- limit,
- next
- })
- });
- const {
- convMessage: {
- m,
- next: newNext
- }
- } = await this._send(command);
- return {
- results: m,
- next: newNext
- };
- }
- /**
- * 将用户加入该对话黑名单
- * @param {String|String[]} clientIds 成员 client id
- * @return {Promise.<PartiallySuccess>} 部分成功结果,包含了成功的 id 列表、失败原因与对应的 id 列表
- */
- async blockMembers(clientIds) {
- this._debug('block', clientIds);
- clientIds = ensureArray(clientIds); // eslint-disable-line no-param-reassign
- const command = new GenericCommand({
- cmd: 'blacklist',
- op: OpType.block,
- blacklistMessage: new BlacklistCommand({
- srcCid: this.id,
- toPids: clientIds
- })
- });
- await this._appendBlacklistSignature(command, 'conversation-block-clients', clientIds);
- const {
- blacklistMessage
- } = await this._send(command);
- return createPartiallySuccess(blacklistMessage);
- }
- /**
- * 将用户移出该对话黑名单
- * @param {String|String[]} clientIds 成员 client id
- * @return {Promise.<PartiallySuccess>} 部分成功结果,包含了成功的 id 列表、失败原因与对应的 id 列表
- */
- async unblockMembers(clientIds) {
- this._debug('unblock', clientIds);
- clientIds = ensureArray(clientIds); // eslint-disable-line no-param-reassign
- const command = new GenericCommand({
- cmd: 'blacklist',
- op: OpType.unblock,
- blacklistMessage: new BlacklistCommand({
- srcCid: this.id,
- toPids: clientIds
- })
- });
- await this._appendBlacklistSignature(command, 'conversation-unblock-clients', clientIds);
- const {
- blacklistMessage
- } = await this._send(command);
- return createPartiallySuccess(blacklistMessage);
- }
- /**
- * 查询该对话黑名单
- * @param {Object} [options]
- * @param {Number} [options.limit] 返回的成员数量,服务器默认值 10
- * @param {String} [options.next] 从指定 next 开始查询,与 limit 一起使用可以完成翻页
- * @return {PagedResults.<string>} 查询结果。其中的 cureser 存在表示还有更多结果。
- */
- async queryBlockedMembers({
- limit,
- next
- } = {}) {
- this._debug('query blocked: limit %O, next: %O', limit, next);
- const command = new GenericCommand({
- cmd: 'blacklist',
- op: OpType.query,
- blacklistMessage: new BlacklistCommand({
- srcCid: this.id,
- limit,
- next
- })
- });
- const {
- blacklistMessage: {
- blockedPids,
- next: newNext
- }
- } = await this._send(command);
- return {
- results: blockedPids,
- next: newNext
- };
- }
- toFullJSON() {
- const {
- creator,
- system,
- transient,
- createdAt,
- updatedAt,
- _attributes
- } = this;
- return { ...super.toFullJSON(),
- creator,
- system,
- transient,
- createdAt: getTime(createdAt),
- updatedAt: getTime(updatedAt),
- ..._attributes
- };
- }
- toJSON() {
- const {
- creator,
- system,
- transient,
- muted,
- mutedMembers,
- createdAt,
- updatedAt,
- _attributes
- } = this;
- return { ...super.toJSON(),
- creator,
- system,
- transient,
- muted,
- mutedMembers,
- createdAt,
- updatedAt,
- ..._attributes
- };
- }
- }
- /**
- * 对话成员角色枚举
- * @enum {String}
- * @since 4.0.0
- * @memberof module:leancloud-realtime
- */
- const ConversationMemberRole = {
- /** 所有者 */
- OWNER: 'Owner',
- /** 管理员 */
- MANAGER: 'Manager',
- /** 成员 */
- MEMBER: 'Member'
- };
- Object.freeze(ConversationMemberRole);
- class ConversationMemberInfo {
- /**
- * 对话成员属性,保存了成员与某个对话相关的属性,对应 _ConversationMemberInfo 表
- * @since 4.0.0
- */
- constructor({
- conversation,
- memberId,
- role
- }) {
- if (!conversation) throw new Error('conversation requried');
- if (!memberId) throw new Error('memberId requried');
- Object.assign(internal(this), {
- conversation,
- memberId,
- role
- });
- }
- /**
- * 对话 Id
- * @type {String}
- * @readonly
- */
- get conversationId() {
- return internal(this).conversation.id;
- }
- /**
- * 成员 Id
- * @type {String}
- * @readonly
- */
- get memberId() {
- return internal(this).memberId;
- }
- /**
- * 角色
- * @type {module:leancloud-realtime.ConversationMemberRole | String}
- * @readonly
- */
- get role() {
- if (this.isOwner) return ConversationMemberRole.OWNER;
- return internal(this).role;
- }
- /**
- * 是否是管理员
- * @type {Boolean}
- * @readonly
- */
- get isOwner() {
- return this.memberId === internal(this).conversation.creator;
- }
- toJSON() {
- const {
- conversationId,
- memberId,
- role,
- isOwner
- } = this;
- return {
- conversationId,
- memberId,
- role,
- isOwner
- };
- }
- }
- /**
- * 普通对话
- *
- * 无法直接实例化,请使用 {@link IMClient#createConversation} 创建新的普通对话。
- * @extends PersistentConversation
- * @public
- */
- class Conversation extends PersistentConversation {
- _addMembers(members) {
- super._addMembers(members);
- this.members = union(this.members, members);
- const {
- memberInfoMap
- } = internal(this);
- if (!memberInfoMap) return;
- members.forEach(memberId => {
- memberInfoMap[memberId] = memberInfoMap[memberId] || new ConversationMemberInfo({
- conversation: this,
- memberId,
- role: ConversationMemberRole.MEMBER
- });
- });
- }
- _removeMembers(members) {
- super._removeMembers(members);
- this.members = difference(this.members, members);
- const {
- memberInfoMap
- } = internal(this);
- if (!memberInfoMap) return;
- members.forEach(memberId => {
- delete memberInfoMap[memberId];
- });
- }
- async _fetchAllMemberInfo() {
- const response = await this._client._requestWithSessionToken({
- method: 'GET',
- path: '/classes/_ConversationMemberInfo',
- query: {
- where: {
- cid: this.id
- }
- }
- });
- const memberInfos = response.results.map(info => new ConversationMemberInfo({
- conversation: this,
- memberId: info.clientId,
- role: info.role
- }));
- const memberInfoMap = {};
- memberInfos.forEach(memberInfo => {
- memberInfoMap[memberInfo.memberId] = memberInfo;
- });
- this.members.forEach(memberId => {
- memberInfoMap[memberId] = memberInfoMap[memberId] || new ConversationMemberInfo({
- conversation: this,
- memberId,
- role: ConversationMemberRole.MEMBER
- });
- });
- internal(this).memberInfoMap = memberInfoMap;
- return memberInfoMap;
- }
- /**
- * 获取所有成员的对话属性
- * @since 4.0.0
- * @return {Promise.<ConversationMemberInfo[]>} 所有成员的对话属性列表
- */
- async getAllMemberInfo({
- noCache = false
- } = {}) {
- let {
- memberInfoMap
- } = internal(this);
- if (!memberInfoMap || noCache) {
- memberInfoMap = await this._fetchAllMemberInfo();
- }
- return this.members.map(memberId => memberInfoMap[memberId]);
- }
- /**
- * 获取指定成员的对话属性
- * @since 4.0.0
- * @param {String} memberId 成员 Id
- * @return {Promise.<ConversationMemberInfo>} 指定成员的对话属性
- */
- async getMemberInfo(memberId) {
- if (this.members.indexOf(memberId) === -1) throw new Error(`${memberId} is not the mumber of conversation[${this.id}]`);
- const {
- memberInfoMap
- } = internal(this);
- if (!(memberInfoMap && memberInfoMap[memberId])) await this.getAllMemberInfo();
- return internal(this).memberInfoMap[memberId];
- }
- /**
- * 更新指定用户的角色
- * @since 4.0.0
- * @param {String} memberId 成员 Id
- * @param {module:leancloud-realtime.ConversationMemberRole | String} role 角色
- * @return {Promise.<this>} self
- */
- async updateMemberRole(memberId, role) {
- this._debug('update member role');
- if (role === ConversationMemberRole.OWNER) throw createError({
- code: ErrorCode.OWNER_PROMOTION_NOT_ALLOWED
- });
- await this._send(new GenericCommand({
- op: OpType.member_info_update,
- convMessage: new ConvCommand({
- targetClientId: memberId,
- info: new ConvMemberInfo({
- pid: memberId,
- role
- })
- })
- }));
- const {
- memberInfos
- } = internal(this);
- if (memberInfos && memberInfos[memberId]) {
- internal(memberInfos[memberId]).role = role;
- }
- return this;
- }
- }
- /**
- * 聊天室。
- *
- * 无法直接实例化,请使用 {@link IMClient#createChatRoom} 创建新的聊天室。
- * @since 4.0.0
- * @extends PersistentConversation
- * @public
- */
- class ChatRoom extends PersistentConversation {}
- /**
- * 服务号。
- *
- * 服务号不支持在客户端创建。
- * @since 4.0.0
- * @extends PersistentConversation
- * @public
- */
- class ServiceConversation extends PersistentConversation {
- /**
- * 订阅该服务号
- * @return {Promise.<this>} self
- */
- async subscribe() {
- return this.join();
- }
- /**
- * 退订该服务号
- * @return {Promise.<this>} self
- */
- async unsubscribe() {
- return this.quit();
- }
- }
- const transformNotFoundError = error => error.code === ErrorCode.CONVERSATION_NOT_FOUND ? createError({
- code: ErrorCode.TEMPORARY_CONVERSATION_EXPIRED
- }) : error;
- /**
- * 临时对话
- * @since 4.0.0
- * @extends ConversationBase
- * @public
- */
- class TemporaryConversation extends ConversationBase {
- /**
- * 无法直接实例化,请使用 {@link IMClient#createTemporaryConversation} 创建新的临时对话。
- */
- constructor(data, {
- expiredAt
- }, client) {
- super({ ...data,
- expiredAt
- }, client);
- }
- /**
- * 对话失效时间
- * @type {Date}
- */
- set expiredAt(value) {
- this._expiredAt = decodeDate(value);
- }
- get expiredAt() {
- return this._expiredAt;
- }
- /**
- * 对话是否已失效
- * @type {Boolean}
- */
- get expired() {
- return this.expiredAt < new Date();
- }
- async _send(...args) {
- if (this.expired) throw createError({
- code: ErrorCode.TEMPORARY_CONVERSATION_EXPIRED
- });
- try {
- return await super._send(...args);
- } catch (error) {
- throw transformNotFoundError(error);
- }
- }
- async send(...args) {
- try {
- return await super.send(...args);
- } catch (error) {
- throw transformNotFoundError(error);
- }
- }
- toFullJSON() {
- const {
- expiredAt
- } = this;
- return { ...super.toFullJSON(),
- expiredAt: getTime(expiredAt)
- };
- }
- toJSON() {
- const {
- expiredAt,
- expired
- } = this;
- return { ...super.toJSON(),
- expiredAt,
- expired
- };
- }
- }
- const debug$9 = d('LC:ConversationQuery');
- class ConversationQuery {
- static _encode(value) {
- if (value instanceof Date) {
- return {
- __type: 'Date',
- iso: value.toJSON()
- };
- }
- if (value instanceof RegExp) {
- return value.source;
- }
- return value;
- }
- static _quote(s) {
- return `\\Q${s.replace('\\E', '\\E\\\\E\\Q')}\\E`;
- }
- static _calculateFlag(options) {
- return ['withLastMessagesRefreshed', 'compact'].reduce( // eslint-disable-next-line no-bitwise
- (prev, key) => (prev << 1) + Boolean(options[key]), 0);
- }
- /**
- * 构造一个用 AND 连接所有查询的 ConversationQuery
- * @param {...ConversationQuery} queries
- * @return {ConversationQuery}
- */
- static and(...queries) {
- if (queries.length < 2) {
- throw new Error('The queries must contain at least two elements');
- }
- if (!queries.every(q => q instanceof ConversationQuery)) {
- throw new Error('The element of queries must be an instance of ConversationQuery');
- }
- const combined = new ConversationQuery(queries[0]._client);
- combined._where.$and = queries.map(q => q._where);
- return combined;
- }
- /**
- * 构造一个用 OR 连接所有查询的 ConversationQuery
- * @param {...ConversationQuery} queries
- * @return {ConversationQuery}
- */
- static or(...queries) {
- const combined = ConversationQuery.and(...queries);
- combined._where.$or = combined._where.$and;
- delete combined._where.$and;
- return combined;
- }
- /**
- * Create a ConversationQuery
- * @param {IMClient} client
- */
- constructor(client) {
- this._client = client;
- this._where = {};
- this._extraOptions = {};
- }
- _addCondition(key, condition, value) {
- // Check if we already have a condition
- if (!this._where[key]) {
- this._where[key] = {};
- }
- this._where[key][condition] = this.constructor._encode(value);
- return this;
- }
- toJSON() {
- const json = {
- where: this._where,
- flag: this.constructor._calculateFlag(this._extraOptions)
- };
- if (typeof this._skip !== 'undefined') json.skip = this._skip;
- if (typeof this._limit !== 'undefined') json.limit = this._limit;
- if (typeof this._order !== 'undefined') json.sort = this._order;
- debug$9(json);
- return json;
- }
- /**
- * 增加查询条件,指定聊天室的组员包含某些成员即可返回
- * @param {string[]} peerIds - 成员 ID 列表
- * @return {ConversationQuery} self
- */
- containsMembers(peerIds) {
- return this.containsAll('m', peerIds);
- }
- /**
- * 增加查询条件,指定聊天室的组员条件满足条件的才返回
- *
- * @param {string[]} - 成员 ID 列表
- * @param {Boolean} includeSelf - 是否包含自己
- * @return {ConversationQuery} self
- */
- withMembers(peerIds, includeSelf) {
- const peerIdsSet = new Set(peerIds);
- if (includeSelf) {
- peerIdsSet.add(this._client.id);
- }
- this.sizeEqualTo('m', peerIdsSet.size);
- return this.containsMembers(Array.from(peerIdsSet));
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段满足等于条件时即可返回
- *
- * @param {string} key
- * @param value
- * @return {ConversationQuery} self
- */
- equalTo(key, value) {
- this._where[key] = this.constructor._encode(value);
- return this;
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段满足小于条件时即可返回
- * @param {string} key
- * @param value
- * @return {ConversationQuery} self
- */
- lessThan(key, value) {
- return this._addCondition(key, '$lt', value);
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段满足小于等于条件时即可返回
- * @param {string} key
- * @param value
- * @return {ConversationQuery} self
- */
- lessThanOrEqualTo(key, value) {
- return this._addCondition(key, '$lte', value);
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段满足大于条件时即可返回
- *
- * @param {string} key
- * @param value
- * @return {ConversationQuery} self
- */
- greaterThan(key, value) {
- return this._addCondition(key, '$gt', value);
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段满足大于等于条件时即可返回
- *
- * @param {string} key
- * @param value
- * @return {ConversationQuery} self
- */
- greaterThanOrEqualTo(key, value) {
- return this._addCondition(key, '$gte', value);
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段满足不等于条件时即可返回
- *
- * @param {string} key
- * @param value
- * @return {ConversationQuery} self
- */
- notEqualTo(key, value) {
- return this._addCondition(key, '$ne', value);
- }
- /**
- * 增加查询条件,当 conversation 存在指定的字段时即可返回
- *
- * @since 3.5.0
- * @param {string} key
- * @return {ConversationQuery} self
- */
- exists(key) {
- return this._addCondition(key, '$exists', true);
- }
- /**
- * 增加查询条件,当 conversation 不存在指定的字段时即可返回
- *
- * @since 3.5.0
- * @param {string} key
- * @return {ConversationQuery} self
- */
- doesNotExist(key) {
- return this._addCondition(key, '$exists', false);
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段对应的值包含在指定值中时即可返回
- *
- * @param {string} key
- * @param values
- * @return {ConversationQuery} self
- */
- containedIn(key, values) {
- return this._addCondition(key, '$in', values);
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段对应的值不包含在指定值中时即可返回
- *
- * @param {string} key
- * @param values
- * @return {ConversationQuery} self
- */
- notContainsIn(key, values) {
- return this._addCondition(key, '$nin', values);
- }
- /**
- * 增加查询条件,当conversation的属性中对应的字段中的元素包含所有的值才可返回
- *
- * @param {string} key
- * @param values
- * @return {ConversationQuery} self
- */
- containsAll(key, values) {
- return this._addCondition(key, '$all', values);
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段对应的值包含此字符串即可返回
- *
- * @param {string} key
- * @param {string} subString
- * @return {ConversationQuery} self
- */
- contains(key, subString) {
- return this._addCondition(key, '$regex', ConversationQuery._quote(subString));
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段对应的值以此字符串起始即可返回
- *
- * @param {string} key
- * @param {string} prefix
- * @return {ConversationQuery} self
- */
- startsWith(key, prefix) {
- return this._addCondition(key, '$regex', `^${ConversationQuery._quote(prefix)}`);
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段对应的值以此字符串结束即可返回
- *
- * @param {string} key
- * @param {string} suffix
- * @return {ConversationQuery} self
- */
- endsWith(key, suffix) {
- return this._addCondition(key, '$regex', `${ConversationQuery._quote(suffix)}$`);
- }
- /**
- * 增加查询条件,当 conversation 的属性中对应的字段对应的值满足提供的正则表达式即可返回
- *
- * @param {string} key
- * @param {RegExp} regex
- * @return {ConversationQuery} self
- */
- matches(key, regex) {
- this._addCondition(key, '$regex', regex); // Javascript regex options support mig as inline options but store them
- // as properties of the object. We support mi & should migrate them to
- // modifiers
- let _modifiers = '';
- if (regex.ignoreCase) {
- _modifiers += 'i';
- }
- if (regex.multiline) {
- _modifiers += 'm';
- }
- if (_modifiers && _modifiers.length) {
- this._addCondition(key, '$options', _modifiers);
- }
- return this;
- }
- /**
- * 添加查询约束条件,查找 key 类型是数组,该数组的长度匹配提供的数值
- *
- * @param {string} key
- * @param {Number} length
- * @return {ConversationQuery} self
- */
- sizeEqualTo(key, length) {
- return this._addCondition(key, '$size', length);
- }
- /**
- * 设置返回集合的大小上限
- *
- * @param {Number} limit - 上限
- * @return {ConversationQuery} self
- */
- limit(limit) {
- this._limit = limit;
- return this;
- }
- /**
- * 设置返回集合的起始位置,一般用于分页
- *
- * @param {Number} skip - 起始位置跳过几个对象
- * @return {ConversationQuery} self
- */
- skip(skip) {
- this._skip = skip;
- return this;
- }
- /**
- * 设置返回集合按照指定key进行增序排列
- *
- * @param {string} key
- * @return {ConversationQuery} self
- */
- ascending(key) {
- this._order = key;
- return this;
- }
- /**
- * 设置返回集合按照指定key进行增序排列,如果已设置其他排序,原排序的优先级较高
- *
- * @param {string} key
- * @return {ConversationQuery} self
- */
- addAscending(key) {
- if (this._order) {
- this._order += `,${key}`;
- } else {
- this._order = key;
- }
- return this;
- }
- /**
- * 设置返回集合按照指定 key 进行降序排列
- *
- * @param {string} key
- * @return {ConversationQuery} self
- */
- descending(key) {
- this._order = `-${key}`;
- return this;
- }
- /**
- * 设置返回集合按照指定 key 进行降序排列,如果已设置其他排序,原排序的优先级较高
- *
- * @param {string} key
- * @return {ConversationQuery} self
- */
- addDescending(key) {
- if (this._order) {
- this._order += `,-${key}`;
- } else {
- this._order = `-${key}`;
- }
- return this;
- }
- /**
- * 设置返回的 conversations 刷新最后一条消息
- * @param {Boolean} [enabled=true]
- * @return {ConversationQuery} self
- */
- withLastMessagesRefreshed(enabled = true) {
- this._extraOptions.withLastMessagesRefreshed = enabled;
- return this;
- }
- /**
- * 设置返回的 conversations 为精简模式,即不含成员列表
- * @param {Boolean} [enabled=true]
- * @return {ConversationQuery} self
- */
- compact(enabled = true) {
- this._extraOptions.compact = enabled;
- return this;
- }
- /**
- * 执行查询
- * @return {Promise.<ConversationBase[]>}
- */
- async find() {
- return this._client._executeQuery(this);
- }
- /**
- * 返回符合条件的第一个结果
- * @return {Promise.<ConversationBase>}
- */
- async first() {
- return (await this.limit(1).find())[0];
- }
- }
- const debug$a = d('LC:SessionManager');
- class SessionManager {
- constructor({
- refresh,
- onBeforeGetSessionToken
- } = {}) {
- this.refresh = refresh;
- this._onBeforeGetSessionToken = onBeforeGetSessionToken;
- this.setSessionToken(null, 0);
- }
- setSessionToken(token, ttl) {
- debug$a('set session token', token, ttl);
- const sessionToken = new Expirable(token, ttl * 1000);
- this._sessionToken = sessionToken;
- delete this._pendingSessionTokenPromise;
- return sessionToken;
- }
- async setSessionTokenAsync(promise) {
- const currentSessionToken = this._sessionToken;
- this._pendingSessionTokenPromise = promise.catch(error => {
- // revert, otherwise the following getSessionToken calls
- // will all be rejected
- this._sessionToken = currentSessionToken;
- throw error;
- });
- return this.setSessionToken(...(await this._pendingSessionTokenPromise));
- }
- async getSessionToken({
- autoRefresh = true
- } = {}) {
- debug$a('get session token');
- if (this._onBeforeGetSessionToken) {
- this._onBeforeGetSessionToken(this);
- }
- const {
- value,
- originalValue
- } = this._sessionToken || (await this._pendingSessionTokenPromise);
- if (value === Expirable.EXPIRED && autoRefresh && this.refresh) {
- debug$a('refresh expired session token');
- const {
- value: newValue
- } = await this.setSessionTokenAsync(this.refresh(this, originalValue));
- debug$a('session token', newValue);
- return newValue;
- }
- debug$a('session token', value);
- return value;
- }
- revoke() {
- if (this._sessionToken) this._sessionToken.expiredAt = -1;
- }
- }
- var _dec$2, _dec2, _class$3;
- const debug$b = d('LC:IMClient');
- const {
- INVITED: INVITED$1,
- KICKED: KICKED$1,
- MEMBERS_JOINED: MEMBERS_JOINED$1,
- MEMBERS_LEFT: MEMBERS_LEFT$1,
- MEMBER_INFO_UPDATED: MEMBER_INFO_UPDATED$1,
- BLOCKED: BLOCKED$1,
- UNBLOCKED: UNBLOCKED$1,
- MEMBERS_BLOCKED: MEMBERS_BLOCKED$1,
- MEMBERS_UNBLOCKED: MEMBERS_UNBLOCKED$1,
- MUTED: MUTED$1,
- UNMUTED: UNMUTED$1,
- MEMBERS_MUTED: MEMBERS_MUTED$1,
- MEMBERS_UNMUTED: MEMBERS_UNMUTED$1,
- MESSAGE: MESSAGE$2,
- UNREAD_MESSAGES_COUNT_UPDATE: UNREAD_MESSAGES_COUNT_UPDATE$1,
- CLOSE: CLOSE$1,
- CONFLICT: CONFLICT$1,
- UNHANDLED_MESSAGE: UNHANDLED_MESSAGE$1,
- CONVERSATION_INFO_UPDATED: CONVERSATION_INFO_UPDATED$1,
- MESSAGE_RECALL: MESSAGE_RECALL$1,
- MESSAGE_UPDATE: MESSAGE_UPDATE$1,
- INFO_UPDATED: INFO_UPDATED$1
- } = IMEvent;
- const isTemporaryConversatrionId = id => /^_tmp:/.test(id);
- /**
- * 1 patch-msg
- * 1 temp-conv-msg
- * 0 auto-bind-deviceid-and-installation
- * 1 transient-msg-ack
- * 1 keep-notification
- * 1 partial-failed-msg
- * 0 group-chat-rcp
- * 1 omit-peer-id
- * @ignore
- */
- const configBitmap = 0b10111011;
- let IMClient = (_dec$2 = throttle(1000), _dec2 = throttle(1000), (_class$3 = class IMClient extends EventEmitter {
- /**
- * 无法直接实例化,请使用 {@link Realtime#createIMClient} 创建新的 IMClient。
- *
- * @extends EventEmitter
- */
- constructor(id, options = {}, props) {
- if (!(id === undefined || typeof id === 'string')) {
- throw new TypeError(`Client id [${id}] is not a String`);
- }
- super();
- Object.assign(this, {
- /**
- * @var id {String} 客户端 id
- * @memberof IMClient#
- */
- id,
- options
- }, props);
- if (!this._messageParser) {
- throw new Error('IMClient must be initialized with a MessageParser');
- }
- this._conversationCache = new Cache(`client:${this.id}`);
- this._ackMessageBuffer = {};
- internal(this).lastPatchTime = Date.now();
- internal(this).lastNotificationTime = undefined;
- internal(this)._eventemitter = new EventEmitter();
- if (debug$b.enabled) {
- values(IMEvent).forEach(event => this.on(event, (...payload) => this._debug(`${event} event emitted. %o`, payload)));
- } // onIMClientCreate hook
- applyDecorators(this._plugins.onIMClientCreate, this);
- }
- _debug(...params) {
- debug$b(...params, `[${this.id}]`);
- }
- /**
- * @override
- * @private
- */
- async _dispatchCommand(command) {
- this._debug(trim(command), 'received');
- if (command.serverTs && command.notificationType === 1) {
- internal(this).lastNotificationTime = getTime(decodeDate(command.serverTs));
- }
- switch (command.cmd) {
- case CommandType.conv:
- return this._dispatchConvMessage(command);
- case CommandType.direct:
- return this._dispatchDirectMessage(command);
- case CommandType.session:
- return this._dispatchSessionMessage(command);
- case CommandType.unread:
- return this._dispatchUnreadMessage(command);
- case CommandType.rcp:
- return this._dispatchRcpMessage(command);
- case CommandType.patch:
- return this._dispatchPatchMessage(command);
- default:
- return this.emit(UNHANDLED_MESSAGE$1, command);
- }
- }
- async _dispatchSessionMessage(message) {
- const {
- sessionMessage: {
- code,
- reason
- }
- } = message;
- switch (message.op) {
- case OpType.closed:
- {
- internal(this)._eventemitter.emit('close');
- if (code === ErrorCode.SESSION_CONFLICT) {
- /**
- * 用户在其他客户端登录,当前客户端被服务端强行下线。详见文档「单点登录」章节。
- * @event IMClient#CONFLICT
- * @param {Object} payload
- * @param {string} payload.reason 原因
- */
- return this.emit(CONFLICT$1, {
- reason
- });
- }
- /**
- * 当前客户端被服务端强行下线
- * @event IMClient#CLOSE
- * @param {Object} payload
- * @param {Number} payload.code 错误码
- * @param {String} payload.reason 原因
- */
- return this.emit(CLOSE$1, {
- code,
- reason
- });
- }
- default:
- this.emit(UNHANDLED_MESSAGE$1, message);
- throw new Error('Unrecognized session command');
- }
- }
- _dispatchUnreadMessage({
- unreadMessage: {
- convs,
- notifTime
- }
- }) {
- internal(this).lastUnreadNotifTime = notifTime; // ensure all converstions are cached
- return this.getConversations(convs.map(conv => conv.cid)).then(() => // update conversations data
- Promise.all(convs.map(({
- cid,
- unread,
- mid,
- timestamp: ts,
- from,
- data,
- binaryMsg,
- patchTimestamp,
- mentioned
- }) => {
- const conversation = this._conversationCache.get(cid); // deleted conversation
- if (!conversation) return null;
- let timestamp;
- if (ts) {
- timestamp = decodeDate(ts);
- conversation.lastMessageAt = timestamp; // eslint-disable-line no-param-reassign
- }
- return (mid ? this._messageParser.parse(binaryMsg || data).then(message => {
- const messageProps = {
- id: mid,
- cid,
- timestamp,
- updatedAt: patchTimestamp,
- from
- };
- Object.assign(message, messageProps);
- conversation.lastMessage = message; // eslint-disable-line no-param-reassign
- }) : Promise.resolve()).then(() => {
- conversation._setUnreadMessagesMentioned(mentioned);
- const countNotUpdated = unread === internal(conversation).unreadMessagesCount;
- if (countNotUpdated) return null; // to be filtered
- // manipulate internal property directly to skip unreadmessagescountupdate event
- internal(conversation).unreadMessagesCount = unread;
- return conversation;
- }); // filter conversations without unread count update
- })).then(conversations => conversations.filter(conversation => conversation))).then(conversations => {
- if (conversations.length) {
- /**
- * 未读消息数目更新
- * @event IMClient#UNREAD_MESSAGES_COUNT_UPDATE
- * @since 3.4.0
- * @param {Conversation[]} conversations 未读消息数目有更新的对话列表
- */
- this.emit(UNREAD_MESSAGES_COUNT_UPDATE$1, conversations);
- }
- });
- }
- async _dispatchRcpMessage(message) {
- const {
- rcpMessage,
- rcpMessage: {
- read
- }
- } = message;
- const conversationId = rcpMessage.cid;
- const messageId = rcpMessage.id;
- const timestamp = decodeDate(rcpMessage.t);
- const conversation = this._conversationCache.get(conversationId); // conversation not cached means the client does not send the message
- // during this session
- if (!conversation) return;
- conversation._handleReceipt({
- messageId,
- timestamp,
- read
- });
- }
- _dispatchPatchMessage({
- patchMessage: {
- patches
- }
- }) {
- // ensure all converstions are cached
- return this.getConversations(patches.map(patch => patch.cid)).then(() => Promise.all(patches.map(({
- cid,
- mid,
- timestamp,
- recall,
- data,
- patchTimestamp,
- from,
- binaryMsg,
- mentionAll,
- mentionPids,
- patchCode,
- patchReason
- }) => {
- const conversation = this._conversationCache.get(cid); // deleted conversation
- if (!conversation) return null;
- return this._messageParser.parse(binaryMsg || data).then(message => {
- const patchTime = getTime(decodeDate(patchTimestamp));
- const messageProps = {
- id: mid,
- cid,
- timestamp,
- updatedAt: patchTime,
- from,
- mentionList: mentionPids,
- mentionedAll: mentionAll
- };
- Object.assign(message, messageProps);
- message._setStatus(MessageStatus.SENT);
- message._updateMentioned(this.id);
- if (internal(this).lastPatchTime < patchTime) {
- internal(this).lastPatchTime = patchTime;
- } // update conversation lastMessage
- if (conversation.lastMessage && conversation.lastMessage.id === mid) {
- conversation.lastMessage = message; // eslint-disable-line no-param-reassign
- }
- let reason;
- if (patchCode) {
- reason = {
- code: patchCode.toNumber(),
- detail: patchReason
- };
- }
- if (recall) {
- /**
- * 消息被撤回
- * @event IMClient#MESSAGE_RECALL
- * @param {AVMessage} message 被撤回的消息
- * @param {ConversationBase} conversation 消息所在的会话
- * @param {PatchReason} [reason] 撤回的原因,不存在代表是发送者主动撤回
- */
- this.emit(MESSAGE_RECALL$1, message, conversation, reason);
- /**
- * 消息被撤回
- * @event ConversationBase#MESSAGE_RECALL
- * @param {AVMessage} message 被撤回的消息
- * @param {PatchReason} [reason] 撤回的原因,不存在代表是发送者主动撤回
- */
- conversation.emit(MESSAGE_RECALL$1, message, reason);
- } else {
- /**
- * 消息被修改
- * @event IMClient#MESSAGE_UPDATE
- * @param {AVMessage} message 被修改的消息
- * @param {ConversationBase} conversation 消息所在的会话
- * @param {PatchReason} [reason] 修改的原因,不存在代表是发送者主动修改
- */
- this.emit(MESSAGE_UPDATE$1, message, conversation, reason);
- /**
- * 消息被修改
- * @event ConversationBase#MESSAGE_UPDATE
- * @param {AVMessage} message 被修改的消息
- * @param {PatchReason} [reason] 修改的原因,不存在代表是发送者主动修改
- */
- conversation.emit(MESSAGE_UPDATE$1, message, reason);
- }
- });
- })));
- }
- async _dispatchConvMessage(message) {
- const {
- convMessage,
- convMessage: {
- initBy,
- m,
- info,
- attr
- }
- } = message;
- const conversation = await this.getConversation(convMessage.cid);
- switch (message.op) {
- case OpType.joined:
- {
- conversation._addMembers([this.id]);
- const payload = {
- invitedBy: initBy
- };
- /**
- * 当前用户被添加至某个对话
- * @event IMClient#INVITED
- * @param {Object} payload
- * @param {String} payload.invitedBy 邀请者 id
- * @param {ConversationBase} conversation
- */
- this.emit(INVITED$1, payload, conversation);
- /**
- * 当前用户被添加至当前对话
- * @event ConversationBase#INVITED
- * @param {Object} payload
- * @param {String} payload.invitedBy 该移除操作的发起者 id
- */
- conversation.emit(INVITED$1, payload);
- return;
- }
- case OpType.left:
- {
- conversation._removeMembers([this.id]);
- const payload = {
- kickedBy: initBy
- };
- /**
- * 当前用户被从某个对话中移除
- * @event IMClient#KICKED
- * @param {Object} payload
- * @param {String} payload.kickedBy 该移除操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(KICKED$1, payload, conversation);
- /**
- * 当前用户被从当前对话中移除
- * @event ConversationBase#KICKED
- * @param {Object} payload
- * @param {String} payload.kickedBy 该移除操作的发起者 id
- */
- conversation.emit(KICKED$1, payload);
- return;
- }
- case OpType.members_joined:
- {
- conversation._addMembers(m);
- const payload = {
- invitedBy: initBy,
- members: m
- };
- /**
- * 有用户被添加至某个对话
- * @event IMClient#MEMBERS_JOINED
- * @param {Object} payload
- * @param {String[]} payload.members 被添加的用户 id 列表
- * @param {String} payload.invitedBy 邀请者 id
- * @param {ConversationBase} conversation
- */
- this.emit(MEMBERS_JOINED$1, payload, conversation);
- /**
- * 有成员被添加至当前对话
- * @event ConversationBase#MEMBERS_JOINED
- * @param {Object} payload
- * @param {String[]} payload.members 被添加的成员 id 列表
- * @param {String} payload.invitedBy 邀请者 id
- */
- conversation.emit(MEMBERS_JOINED$1, payload);
- return;
- }
- case OpType.members_left:
- {
- conversation._removeMembers(m);
- const payload = {
- kickedBy: initBy,
- members: m
- };
- /**
- * 有成员被从某个对话中移除
- * @event IMClient#MEMBERS_LEFT
- * @param {Object} payload
- * @param {String[]} payload.members 被移除的成员 id 列表
- * @param {String} payload.kickedBy 该移除操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(MEMBERS_LEFT$1, payload, conversation);
- /**
- * 有成员被从当前对话中移除
- * @event ConversationBase#MEMBERS_LEFT
- * @param {Object} payload
- * @param {String[]} payload.members 被移除的成员 id 列表
- * @param {String} payload.kickedBy 该移除操作的发起者 id
- */
- conversation.emit(MEMBERS_LEFT$1, payload);
- return;
- }
- case OpType.members_blocked:
- {
- const payload = {
- blockedBy: initBy,
- members: m
- };
- /**
- * 有成员被加入某个对话的黑名单
- * @event IMClient#MEMBERS_BLOCKED
- * @param {Object} payload
- * @param {String[]} payload.members 成员 id 列表
- * @param {String} payload.blockedBy 该操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(MEMBERS_BLOCKED$1, payload, conversation);
- /**
- * 有成员被加入当前对话的黑名单
- * @event ConversationBase#MEMBERS_BLOCKED
- * @param {Object} payload
- * @param {String[]} payload.members 成员 id 列表
- * @param {String} payload.blockedBy 该操作的发起者 id
- */
- conversation.emit(MEMBERS_BLOCKED$1, payload);
- return;
- }
- case OpType.members_unblocked:
- {
- const payload = {
- unblockedBy: initBy,
- members: m
- };
- /**
- * 有成员被移出某个对话的黑名单
- * @event IMClient#MEMBERS_UNBLOCKED
- * @param {Object} payload
- * @param {String[]} payload.members 成员 id 列表
- * @param {String} payload.unblockedBy 该操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(MEMBERS_UNBLOCKED$1, payload, conversation);
- /**
- * 有成员被移出当前对话的黑名单
- * @event ConversationBase#MEMBERS_UNBLOCKED
- * @param {Object} payload
- * @param {String[]} payload.members 成员 id 列表
- * @param {String} payload.unblockedBy 该操作的发起者 id
- */
- conversation.emit(MEMBERS_UNBLOCKED$1, payload);
- return;
- }
- case OpType.blocked:
- {
- const payload = {
- blockedBy: initBy
- };
- /**
- * 当前用户被加入某个对话的黑名单
- * @event IMClient#BLOCKED
- * @param {Object} payload
- * @param {String} payload.blockedBy 该操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(BLOCKED$1, payload, conversation);
- /**
- * 当前用户被加入当前对话的黑名单
- * @event ConversationBase#BLOCKED
- * @param {Object} payload
- * @param {String} payload.blockedBy 该操作的发起者 id
- */
- conversation.emit(BLOCKED$1, payload);
- return;
- }
- case OpType.unblocked:
- {
- const payload = {
- unblockedBy: initBy
- };
- /**
- * 当前用户被移出某个对话的黑名单
- * @event IMClient#UNBLOCKED
- * @param {Object} payload
- * @param {String} payload.unblockedBy 该操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(UNBLOCKED$1, payload, conversation);
- /**
- * 当前用户被移出当前对话的黑名单
- * @event ConversationBase#UNBLOCKED
- * @param {Object} payload
- * @param {String} payload.unblockedBy 该操作的发起者 id
- */
- conversation.emit(UNBLOCKED$1, payload);
- return;
- }
- case OpType.members_shutuped:
- {
- const payload = {
- mutedBy: initBy,
- members: m
- };
- /**
- * 有成员在某个对话中被禁言
- * @event IMClient#MEMBERS_MUTED
- * @param {Object} payload
- * @param {String[]} payload.members 成员 id 列表
- * @param {String} payload.mutedBy 该操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(MEMBERS_MUTED$1, payload, conversation);
- /**
- * 有成员在当前对话中被禁言
- * @event ConversationBase#MEMBERS_MUTED
- * @param {Object} payload
- * @param {String[]} payload.members 成员 id 列表
- * @param {String} payload.mutedBy 该操作的发起者 id
- */
- conversation.emit(MEMBERS_MUTED$1, payload);
- return;
- }
- case OpType.members_unshutuped:
- {
- const payload = {
- unmutedBy: initBy,
- members: m
- };
- /**
- * 有成员在某个对话中被解除禁言
- * @event IMClient#MEMBERS_UNMUTED
- * @param {Object} payload
- * @param {String[]} payload.members 成员 id 列表
- * @param {String} payload.unmutedBy 该操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(MEMBERS_UNMUTED$1, payload, conversation);
- /**
- * 有成员在当前对话中被解除禁言
- * @event ConversationBase#MEMBERS_UNMUTED
- * @param {Object} payload
- * @param {String[]} payload.members 成员 id 列表
- * @param {String} payload.unmutedBy 该操作的发起者 id
- */
- conversation.emit(MEMBERS_UNMUTED$1, payload);
- return;
- }
- case OpType.shutuped:
- {
- const payload = {
- mutedBy: initBy
- };
- /**
- * 有成员在某个对话中被禁言
- * @event IMClient#MUTED
- * @param {Object} payload
- * @param {String} payload.mutedBy 该操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(MUTED$1, payload, conversation);
- /**
- * 有成员在当前对话中被禁言
- * @event ConversationBase#MUTED
- * @param {Object} payload
- * @param {String} payload.mutedBy 该操作的发起者 id
- */
- conversation.emit(MUTED$1, payload);
- return;
- }
- case OpType.unshutuped:
- {
- const payload = {
- unmutedBy: initBy
- };
- /**
- * 有成员在某个对话中被解除禁言
- * @event IMClient#UNMUTED
- * @param {Object} payload
- * @param {String} payload.unmutedBy 该操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(UNMUTED$1, payload, conversation);
- /**
- * 有成员在当前对话中被解除禁言
- * @event ConversationBase#UNMUTED
- * @param {Object} payload
- * @param {String} payload.unmutedBy 该操作的发起者 id
- */
- conversation.emit(UNMUTED$1, payload);
- return;
- }
- case OpType.member_info_changed:
- {
- const {
- pid,
- role
- } = info;
- const {
- memberInfoMap
- } = internal(conversation); // 如果不存在缓存,且不是 role 的更新,则不通知
- if (!memberInfoMap && !role) return;
- const memberInfo = await conversation.getMemberInfo(pid);
- internal(memberInfo).role = role;
- const payload = {
- member: pid,
- memberInfo,
- updatedBy: initBy
- };
- /**
- * 有成员的对话信息被更新
- * @event IMClient#MEMBER_INFO_UPDATED
- * @param {Object} payload
- * @param {String} payload.member 被更新对话信息的成员 id
- * @param {ConversationMumberInfo} payload.memberInfo 被更新的成员对话信息
- * @param {String} payload.updatedBy 该操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(MEMBER_INFO_UPDATED$1, payload, conversation);
- /**
- * 有成员的对话信息被更新
- * @event ConversationBase#MEMBER_INFO_UPDATED
- * @param {Object} payload
- * @param {String} payload.member 被更新对话信息的成员 id
- * @param {ConversationMumberInfo} payload.memberInfo 被更新的成员对话信息
- * @param {String} payload.updatedBy 该操作的发起者 id
- */
- conversation.emit(MEMBER_INFO_UPDATED$1, payload);
- return;
- }
- case OpType.updated:
- {
- const attributes = decode(JSON.parse(attr.data));
- conversation._updateServerAttributes(attributes);
- const payload = {
- attributes,
- updatedBy: initBy
- };
- /**
- * 该对话信息被更新
- * @event IMClient#CONVERSATION_INFO_UPDATED
- * @param {Object} payload
- * @param {Object} payload.attributes 被更新的属性
- * @param {String} payload.updatedBy 该操作的发起者 id
- * @param {ConversationBase} conversation
- */
- this.emit(CONVERSATION_INFO_UPDATED$1, payload, conversation);
- /**
- * 有对话信息被更新
- * @event ConversationBase#INFO_UPDATED
- * @param {Object} payload
- * @param {Object} payload.attributes 被更新的属性
- * @param {String} payload.updatedBy 该操作的发起者 id
- */
- conversation.emit(INFO_UPDATED$1, payload);
- return;
- }
- default:
- this.emit(UNHANDLED_MESSAGE$1, message);
- throw new Error('Unrecognized conversation command');
- }
- }
- _dispatchDirectMessage(originalMessage) {
- const {
- directMessage,
- directMessage: {
- id,
- cid,
- fromPeerId,
- timestamp,
- transient,
- patchTimestamp,
- mentionPids,
- mentionAll,
- binaryMsg,
- msg
- }
- } = originalMessage;
- const content = binaryMsg ? binaryMsg.toArrayBuffer() : msg;
- return Promise.all([this.getConversation(directMessage.cid), this._messageParser.parse(content)]).then(([conversation, message]) => {
- // deleted conversation
- if (!conversation) return undefined;
- const messageProps = {
- id,
- cid,
- timestamp,
- updatedAt: patchTimestamp,
- from: fromPeerId,
- mentionList: mentionPids,
- mentionedAll: mentionAll
- };
- Object.assign(message, messageProps);
- message._updateMentioned(this.id);
- message._setStatus(MessageStatus.SENT); // filter outgoing message sent from another device
- if (message.from !== this.id) {
- if (!(transient || conversation.transient)) {
- this._sendAck(message);
- }
- }
- return this._dispatchParsedMessage(message, conversation);
- });
- }
- _dispatchParsedMessage(message, conversation) {
- // beforeMessageDispatch hook
- return applyDispatcher(this._plugins.beforeMessageDispatch, [message, conversation]).then(shouldDispatch => {
- if (shouldDispatch === false) return;
- conversation.lastMessage = message; // eslint-disable-line no-param-reassign
- conversation.lastMessageAt = message.timestamp; // eslint-disable-line no-param-reassign
- // filter outgoing message sent from another device
- if (message.from !== this.id) {
- conversation.unreadMessagesCount += 1; // eslint-disable-line no-param-reassign
- if (message.mentioned) conversation._setUnreadMessagesMentioned(true);
- }
- /**
- * 当前用户收到消息
- * @event IMClient#MESSAGE
- * @param {Message} message
- * @param {ConversationBase} conversation 收到消息的对话
- */
- this.emit(MESSAGE$2, message, conversation);
- /**
- * 当前对话收到消息
- * @event ConversationBase#MESSAGE
- * @param {Message} message
- */
- conversation.emit(MESSAGE$2, message);
- });
- }
- _sendAck(message) {
- this._debug('send ack for %O', message);
- const {
- cid
- } = message;
- if (!cid) {
- throw new Error('missing cid');
- }
- if (!this._ackMessageBuffer[cid]) {
- this._ackMessageBuffer[cid] = [];
- }
- this._ackMessageBuffer[cid].push(message);
- return this._doSendAck();
- } // jsdoc-ignore-start
- // jsdoc-ignore-end
- _doSendAck() {
- // if not connected, just skip everything
- if (!this._connection.is('connected')) return;
- this._debug('do send ack %O', this._ackMessageBuffer);
- Promise.all(Object.keys(this._ackMessageBuffer).map(cid => {
- const convAckMessages = this._ackMessageBuffer[cid];
- const timestamps = convAckMessages.map(message => message.timestamp);
- const command = new GenericCommand({
- cmd: 'ack',
- ackMessage: new AckCommand({
- cid,
- fromts: Math.min.apply(null, timestamps),
- tots: Math.max.apply(null, timestamps)
- })
- });
- delete this._ackMessageBuffer[cid];
- return this._send(command, false).catch(error => {
- this._debug('send ack failed: %O', error);
- this._ackMessageBuffer[cid] = convAckMessages;
- });
- }));
- }
- _omitPeerId(value) {
- internal(this).peerIdOmittable = value;
- }
- _send(cmd, ...args) {
- const command = cmd;
- if (!internal(this).peerIdOmittable && this.id) {
- command.peerId = this.id;
- }
- return this._connection.send(command, ...args);
- }
- async _open(appId, tag, deviceId, isReconnect = false) {
- this._debug('open session');
- const {
- lastUnreadNotifTime,
- lastPatchTime,
- lastNotificationTime
- } = internal(this);
- const command = new GenericCommand({
- cmd: 'session',
- op: 'open',
- appId,
- peerId: this.id,
- sessionMessage: new SessionCommand({
- ua: `js/${version}`,
- r: isReconnect,
- lastUnreadNotifTime,
- lastPatchTime,
- configBitmap
- })
- });
- if (!isReconnect) {
- Object.assign(command.sessionMessage, trim({
- tag,
- deviceId
- }));
- if (this.options.signatureFactory) {
- const signatureResult = await runSignatureFactory(this.options.signatureFactory, [this._identity]);
- Object.assign(command.sessionMessage, keyRemap({
- signature: 's',
- timestamp: 't',
- nonce: 'n'
- }, signatureResult));
- }
- } else {
- const sessionToken = await this._sessionManager.getSessionToken({
- autoRefresh: false
- });
- if (sessionToken && sessionToken !== Expirable.EXPIRED) {
- Object.assign(command.sessionMessage, {
- st: sessionToken
- });
- }
- }
- let resCommand;
- try {
- resCommand = await this._send(command);
- } catch (error) {
- if (error.code === ErrorCode.SESSION_TOKEN_EXPIRED) {
- if (!this._sessionManager) {
- // let it fail if sessoinToken not cached but command rejected as token expired
- // to prevent session openning flood
- throw new Error('Unexpected session expiration');
- }
- debug$b('Session token expired, reopening');
- this._sessionManager.revoke();
- return this._open(appId, tag, deviceId, isReconnect);
- }
- throw error;
- }
- const {
- peerId,
- sessionMessage,
- sessionMessage: {
- st: token,
- stTtl: tokenTTL,
- code
- },
- serverTs
- } = resCommand;
- if (code) {
- throw createError(sessionMessage);
- }
- if (peerId) {
- this.id = peerId;
- if (!this._identity) this._identity = peerId;
- if (token) {
- this._sessionManager = this._sessionManager || this._createSessionManager();
- this._sessionManager.setSessionToken(token, tokenTTL);
- }
- const serverTime = getTime(decodeDate(serverTs));
- if (serverTs) {
- internal(this).lastPatchTime = serverTime;
- }
- if (lastNotificationTime) {
- // Do not await for it as this is failable
- this._syncNotifications(lastNotificationTime).catch(error => console.warn('Syncing notifications failed:', error));
- } else {
- // Set timestamp to now for next reconnection
- internal(this).lastNotificationTime = serverTime;
- }
- } else {
- console.warn('Unexpected session opened without peerId.');
- }
- return undefined;
- }
- async _syncNotifications(timestamp) {
- const {
- hasMore,
- notifications
- } = await this._fetchNotifications(timestamp);
- notifications.forEach(notification => {
- const {
- cmd,
- op,
- serverTs,
- notificationType,
- ...payload
- } = notification;
- this._dispatchCommand({
- cmd: CommandType[cmd],
- op: OpType[op],
- serverTs,
- notificationType,
- [`${cmd}Message`]: payload
- });
- });
- if (hasMore) {
- return this._syncNotifications(internal(this).lastNotificationTime);
- }
- return undefined;
- }
- async _fetchNotifications(timestamp) {
- return this._requestWithSessionToken({
- method: 'GET',
- path: '/rtm/notifications',
- query: {
- start_ts: timestamp,
- notification_type: 'permanent'
- }
- });
- }
- _createSessionManager() {
- debug$b('create SessionManager');
- return new SessionManager({
- onBeforeGetSessionToken: this._connection.checkConnectionAvailability.bind(this._connection),
- refresh: (manager, expiredSessionToken) => manager.setSessionTokenAsync(Promise.resolve(new GenericCommand({
- cmd: 'session',
- op: 'refresh',
- sessionMessage: new SessionCommand({
- ua: `js/${version}`,
- st: expiredSessionToken
- })
- })).then(async command => {
- if (this.options.signatureFactory) {
- const signatureResult = await runSignatureFactory(this.options.signatureFactory, [this._identity]);
- Object.assign(command.sessionMessage, keyRemap({
- signature: 's',
- timestamp: 't',
- nonce: 'n'
- }, signatureResult));
- }
- return command;
- }).then(this._send.bind(this)).then(({
- sessionMessage: {
- st: token,
- stTtl: ttl
- }
- }) => [token, ttl]))
- });
- }
- async _requestWithSessionToken({
- headers,
- query,
- ...params
- }) {
- const sessionToken = await this._sessionManager.getSessionToken();
- return this._request({
- headers: {
- 'X-LC-IM-Session-Token': sessionToken,
- ...headers
- },
- query: {
- client_id: this.id,
- ...query
- },
- ...params
- });
- }
- /**
- * 关闭客户端
- * @return {Promise}
- */
- async close() {
- this._debug('close session');
- const _ee = internal(this)._eventemitter;
- _ee.emit('beforeclose');
- if (this._connection.is('connected')) {
- const command = new GenericCommand({
- cmd: 'session',
- op: 'close'
- });
- await this._send(command);
- }
- _ee.emit('close');
- this.emit(CLOSE$1, {
- code: 0
- });
- }
- /**
- * 获取 client 列表中在线的 client,每次查询最多 20 个 clientId,超出部分会被忽略
- * @param {String[]} clientIds 要查询的 client ids
- * @return {Primse.<String[]>} 在线的 client ids
- */
- async ping(clientIds) {
- this._debug('ping');
- if (!(clientIds instanceof Array)) {
- throw new TypeError(`clientIds ${clientIds} is not an Array`);
- }
- if (!clientIds.length) {
- return Promise.resolve([]);
- }
- const command = new GenericCommand({
- cmd: 'session',
- op: 'query',
- sessionMessage: new SessionCommand({
- sessionPeerIds: clientIds
- })
- });
- const resCommand = await this._send(command);
- return resCommand.sessionMessage.onlineSessionPeerIds;
- }
- /**
- * 获取某个特定的对话
- * @param {String} id 对话 id,对应 _Conversation 表中的 objectId
- * @param {Boolean} [noCache=false] 强制不从缓存中获取
- * @return {Promise.<ConversationBase>} 如果 id 对应的对话不存在则返回 null
- */
- async getConversation(id, noCache = false) {
- if (typeof id !== 'string') {
- throw new TypeError(`${id} is not a String`);
- }
- if (!noCache) {
- const cachedConversation = this._conversationCache.get(id);
- if (cachedConversation) {
- return cachedConversation;
- }
- }
- if (isTemporaryConversatrionId(id)) {
- return (await this._getTemporaryConversations([id]))[0] || null;
- }
- return this.getQuery().equalTo('objectId', id).find().then(conversations => conversations[0] || null);
- }
- /**
- * 通过 id 批量获取某个特定的对话
- * @since 3.4.0
- * @param {String[]} ids 对话 id 列表,对应 _Conversation 表中的 objectId
- * @param {Boolean} [noCache=false] 强制不从缓存中获取
- * @return {Promise.<ConversationBase[]>} 如果 id 对应的对话不存在则返回 null
- */
- async getConversations(ids, noCache = false) {
- const remoteConversationIds = noCache ? ids : ids.filter(id => this._conversationCache.get(id) === null);
- if (remoteConversationIds.length) {
- const remoteTemporaryConversationIds = remove(remoteConversationIds, isTemporaryConversatrionId);
- const query = [];
- if (remoteConversationIds.length) {
- query.push(this.getQuery().containedIn('objectId', remoteConversationIds).limit(999).find());
- }
- if (remoteTemporaryConversationIds.length) {
- const remoteTemporaryConversationsPromise = remoteTemporaryConversationIds.map(this._getTemporaryConversations.bind(this));
- query.push(...remoteTemporaryConversationsPromise);
- }
- await Promise.all(query);
- }
- return ids.map(id => this._conversationCache.get(id));
- }
- async _getTemporaryConversations(ids) {
- const command = new GenericCommand({
- cmd: 'conv',
- op: 'query',
- convMessage: new ConvCommand({
- tempConvIds: ids
- })
- });
- const resCommand = await this._send(command);
- return this._handleQueryResults(resCommand);
- }
- /**
- * 构造一个 ConversationQuery 来查询对话
- * @return {ConversationQuery.<PersistentConversation>}
- */
- getQuery() {
- return new ConversationQuery(this);
- }
- /**
- * 构造一个 ConversationQuery 来查询聊天室
- * @return {ConversationQuery.<ChatRoom>}
- */
- getChatRoomQuery() {
- return this.getQuery().equalTo('tr', true);
- }
- /**
- * 构造一个 ConversationQuery 来查询服务号
- * @return {ConversationQuery.<ServiceConversation>}
- */
- getServiceConversationQuery() {
- return this.getQuery().equalTo('sys', true);
- }
- async _executeQuery(query) {
- const queryJSON = query.toJSON();
- queryJSON.where = new JsonObjectMessage({
- data: JSON.stringify(encode(queryJSON.where))
- });
- const command = new GenericCommand({
- cmd: 'conv',
- op: 'query',
- convMessage: new ConvCommand(queryJSON)
- });
- const resCommand = await this._send(command);
- return this._handleQueryResults(resCommand);
- }
- async _handleQueryResults(resCommand) {
- let conversations;
- try {
- conversations = decode(JSON.parse(resCommand.convMessage.results.data));
- } catch (error) {
- const commandString = JSON.stringify(trim(resCommand));
- throw new Error(`Parse query result failed: ${error.message}. Command: ${commandString}`);
- }
- conversations = await Promise.all(conversations.map(this._parseConversationFromRawData.bind(this)));
- return conversations.map(this._upsertConversationToCache.bind(this));
- }
- _upsertConversationToCache(fetchedConversation) {
- let conversation = this._conversationCache.get(fetchedConversation.id);
- if (!conversation) {
- conversation = fetchedConversation;
- this._debug('no match, set cache');
- this._conversationCache.set(fetchedConversation.id, fetchedConversation);
- } else {
- this._debug('update cached conversation');
- ['creator', 'createdAt', 'updatedAt', 'lastMessageAt', 'lastMessage', 'mutedMembers', 'members', '_attributes', 'transient', 'muted'].forEach(key => {
- const value = fetchedConversation[key];
- if (value !== undefined) conversation[key] = value;
- });
- if (conversation._reset) conversation._reset();
- }
- return conversation;
- }
- /**
- * 反序列化消息,与 {@link Message#toFullJSON} 相对。
- * @param {Object}
- * @return {AVMessage} 解析后的消息
- * @since 4.0.0
- */
- async parseMessage({
- data,
- bin = false,
- ...properties
- }) {
- const content = bin ? base64Arraybuffer.decode(data) : data;
- const message = await this._messageParser.parse(content);
- Object.assign(message, properties);
- message._updateMentioned(this.id);
- return message;
- }
- /**
- * 反序列化对话,与 {@link Conversation#toFullJSON} 相对。
- * @param {Object}
- * @return {ConversationBase} 解析后的对话
- * @since 4.0.0
- */
- async parseConversation({
- id,
- lastMessageAt,
- lastMessage,
- lastDeliveredAt,
- lastReadAt,
- unreadMessagesCount,
- members,
- mentioned,
- ...properties
- }) {
- const conversationData = {
- id,
- lastMessageAt,
- lastMessage,
- lastDeliveredAt,
- lastReadAt,
- unreadMessagesCount,
- members,
- mentioned
- };
- if (lastMessage) {
- conversationData.lastMessage = await this.parseMessage(lastMessage);
- conversationData.lastMessage._setStatus(MessageStatus.SENT);
- }
- const {
- transient,
- system,
- expiredAt
- } = properties;
- if (transient) return new ChatRoom(conversationData, properties, this);
- if (system) return new ServiceConversation(conversationData, properties, this);
- if (expiredAt || isTemporaryConversatrionId(id)) {
- return new TemporaryConversation(conversationData, {
- expiredAt
- }, this);
- }
- return new Conversation(conversationData, properties, this);
- }
- async _parseConversationFromRawData(rawData) {
- const data = keyRemap({
- objectId: 'id',
- lm: 'lastMessageAt',
- m: 'members',
- tr: 'transient',
- sys: 'system',
- c: 'creator',
- mu: 'mutedMembers'
- }, rawData);
- if (data.msg) {
- data.lastMessage = {
- data: data.msg,
- bin: data.bin,
- from: data.msg_from,
- id: data.msg_mid,
- timestamp: data.msg_timestamp,
- updatedAt: data.patch_timestamp
- };
- delete data.lastMessageFrom;
- delete data.lastMessageId;
- delete data.lastMessageTimestamp;
- delete data.lastMessagePatchTimestamp;
- }
- const {
- ttl
- } = data;
- if (ttl) data.expiredAt = Date.now() + ttl * 1000;
- return this.parseConversation(data);
- }
- /**
- * 创建一个对话
- * @param {Object} options 除了下列字段外的其他字段将被视为对话的自定义属性
- * @param {String[]} options.members 对话的初始成员列表,默认包含当前 client
- * @param {String} [options.name] 对话的名字
- * @param {Boolean} [options.unique=true] 唯一对话,当其为 true 时,如果当前已经有相同成员的对话存在则返回该对话,否则会创建新的对话
- * @return {Promise.<Conversation>}
- */
- async createConversation({
- members: m,
- name,
- transient,
- unique = true,
- _tempConv: tempConv,
- _tempConvTTL: tempConvTTL,
- ...properties
- } = {}) {
- if (!(transient || Array.isArray(m))) {
- throw new TypeError(`conversation members ${m} is not an array`);
- }
- let members = new Set(m);
- members.add(this.id);
- members = Array.from(members).sort();
- let attr = properties || {};
- if (name) {
- if (typeof name !== 'string') {
- throw new TypeError(`conversation name ${name} is not a string`);
- }
- attr.name = name;
- }
- attr = new JsonObjectMessage({
- data: JSON.stringify(encode(attr))
- });
- const startCommandJson = {
- m: members,
- attr,
- transient,
- unique,
- tempConv,
- tempConvTTL
- };
- const command = new GenericCommand({
- cmd: 'conv',
- op: 'start',
- convMessage: new ConvCommand(startCommandJson)
- });
- if (this.options.conversationSignatureFactory) {
- const params = [null, this._identity, members, 'create'];
- const signatureResult = await runSignatureFactory(this.options.conversationSignatureFactory, params);
- Object.assign(command.convMessage, keyRemap({
- signature: 's',
- timestamp: 't',
- nonce: 'n'
- }, signatureResult));
- }
- const {
- convMessage: {
- cid,
- cdate,
- tempConvTTL: ttl
- }
- } = await this._send(command);
- const data = {
- name,
- transient,
- unique,
- id: cid,
- createdAt: cdate,
- updatedAt: cdate,
- lastMessageAt: null,
- creator: this.id,
- members: transient ? [] : members,
- ...properties
- };
- if (ttl) data.expiredAt = Date.now() + ttl * 1000;
- const conversation = await this.parseConversation(data);
- return this._upsertConversationToCache(conversation);
- }
- /**
- * 创建一个聊天室
- * @since 4.0.0
- * @param {Object} options 除了下列字段外的其他字段将被视为对话的自定义属性
- * @param {String} [options.name] 对话的名字
- * @return {Promise.<ChatRoom>}
- */
- async createChatRoom(param) {
- return this.createConversation({ ...param,
- transient: true,
- members: null,
- unique: false,
- _tempConv: false
- });
- }
- /**
- * 创建一个临时对话
- * @since 4.0.0
- * @param {Object} options
- * @param {String[]} options.members 对话的初始成员列表,默认包含当前 client
- * @param {String} [options.ttl] 对话存在时间,单位为秒,最大值与默认值均为 86400(一天),过期后该对话不再可用。
- * @return {Promise.<TemporaryConversation>}
- */
- async createTemporaryConversation({
- ttl: _tempConvTTL,
- ...param
- }) {
- return this.createConversation({ ...param,
- _tempConv: true,
- _tempConvTTL
- });
- } // jsdoc-ignore-start
- // jsdoc-ignore-end
- _doSendRead() {
- // if not connected, just skip everything
- if (!this._connection.is('connected')) return;
- const buffer = internal(this).readConversationsBuffer;
- const conversations = Array.from(buffer);
- if (!conversations.length) return;
- const ids = conversations.map(conversation => {
- if (!(conversation instanceof ConversationBase)) {
- throw new TypeError(`${conversation} is not a Conversation`);
- }
- return conversation.id;
- });
- this._debug(`mark [${ids}] as read`);
- buffer.clear();
- this._sendReadCommand(conversations).catch(error => {
- this._debug('send read failed: %O', error);
- conversations.forEach(buffer.add.bind(buffer));
- });
- }
- _sendReadCommand(conversations) {
- return this._send(new GenericCommand({
- cmd: 'read',
- readMessage: new ReadCommand({
- convs: conversations.map(conversation => new ReadTuple({
- cid: conversation.id,
- mid: conversation.lastMessage && conversation.lastMessage.from !== this.id ? conversation.lastMessage.id : undefined,
- timestamp: (conversation.lastMessageAt || new Date()).getTime()
- }))
- })
- }), false);
- }
- }, (_applyDecoratedDescriptor(_class$3.prototype, "_doSendAck", [_dec$2], Object.getOwnPropertyDescriptor(_class$3.prototype, "_doSendAck"), _class$3.prototype), _applyDecoratedDescriptor(_class$3.prototype, "_doSendRead", [_dec2], Object.getOwnPropertyDescriptor(_class$3.prototype, "_doSendRead"), _class$3.prototype)), _class$3));
- /**
- * 修改、撤回消息的原因
- * @typedef PatchReason
- * @type {Object}
- * @property {number} code 负数为内置 code,正数为开发者在 hook 中自定义的 code。比如因为敏感词过滤被修改的 code 为 -4408。
- * @property {string} [detail] 具体的原因说明。
- */
- const RECONNECT_ERROR = 'reconnecterror';
- var CoreEvent = /*#__PURE__*/Object.freeze({
- __proto__: null,
- RECONNECT_ERROR: RECONNECT_ERROR,
- DISCONNECT: DISCONNECT,
- RECONNECT: RECONNECT,
- RETRY: RETRY,
- SCHEDULE: SCHEDULE,
- OFFLINE: OFFLINE,
- ONLINE: ONLINE
- });
- var _class$4;
- let // jsdoc-ignore-end
- BinaryMessage = IE10Compatible(_class$4 = class BinaryMessage extends Message {
- /**
- * 二进制消息
- * @extends Message
- * @param {ArrayBuffer} buffer
- * @since 4.0.0
- */
- constructor(buffer) {
- if (!(buffer instanceof ArrayBuffer)) {
- throw new TypeError(`${buffer} is not an ArrayBuffer`);
- }
- super(buffer);
- }
- /**
- * @type ArrayBuffer
- */
- get buffer() {
- return this.content;
- }
- set buffer(buffer) {
- this.content = buffer;
- }
- static validate(target) {
- return target instanceof ArrayBuffer;
- }
- toJSON() {
- return { ...super._toJSON(),
- data: base64Arraybuffer.encode(this.content)
- };
- }
- toFullJSON() {
- return { ...super.toFullJSON(),
- bin: true,
- data: base64Arraybuffer.encode(this.content)
- };
- }
- }) || _class$4;
- var _dec$3, _class$5;
- let // jsdoc-ignore-end
- TextMessage = (_dec$3 = messageType(-1), _dec$3(_class$5 = IE10Compatible(_class$5 = class TextMessage extends TypedMessage {
- /**
- * 文类类型消息
- * @extends TypedMessage
- * @param {String} [text='']
- * @throws {TypeError} text 不是 String 类型
- */
- constructor(text = '') {
- if (typeof text !== 'string') {
- throw new TypeError(`${text} is not a string`);
- }
- super();
- this.setText(text);
- }
- }) || _class$5) || _class$5);
- /**
- * @name TYPE
- * @memberof TextMessage
- * @type Number
- * @static
- * @const
- */
- var _class$6;
- const debug$c = d('LC:MessageParser');
- const tryParseJson = (target, key, descriptor) => {
- const fn = descriptor.value; // eslint-disable-next-line no-param-reassign
- descriptor.value = function wrapper(param) {
- let content;
- if (typeof param !== 'string') {
- content = param;
- } else {
- try {
- content = JSON.parse(param);
- } catch (error) {
- content = param;
- }
- }
- return fn.call(this, content);
- };
- };
- const applyPlugins = (target, key, descriptor) => {
- const fn = descriptor.value; // eslint-disable-next-line no-param-reassign
- descriptor.value = function wrapper(json) {
- return Promise.resolve(json).then(applyMiddlewares(this._plugins.beforeMessageParse)).then(decoratedJson => fn.call(this, decoratedJson)).then(applyMiddlewares(this._plugins.afterMessageParse));
- };
- };
- let MessageParser = (_class$6 = class MessageParser {
- /**
- * 消息解析器
- * @param {Object} plugins 插件,插件的 messageClasses 会自动被注册,在解析时 beforeMessageParse 与 afterMessageParse Middleware 会被应用。
- */
- constructor(plugins = {}) {
- this._plugins = plugins;
- this._messageClasses = [];
- this.register(plugins.messageClasses);
- }
- /**
- * 注册消息类
- *
- * @param {Function | Function[]} messageClass 消息类,需要实现 {@link AVMessage} 接口,
- * 建议继承自 {@link TypedMessage},也可以传入一个消息类数组。
- * @throws {TypeError} 如果 messageClass 没有实现 {@link AVMessage} 接口则抛出异常
- */
- register(messageClasses) {
- ensureArray(messageClasses).map(klass => this._register(klass));
- }
- _register(messageClass) {
- if (messageClass && messageClass.parse && messageClass.prototype && messageClass.prototype.getPayload) {
- this._messageClasses.unshift(messageClass);
- } else {
- throw new TypeError('Invalid messageClass');
- }
- } // jsdoc-ignore-start
- // jsdoc-ignore-end
- /**
- * 解析消息内容
- * @param {Object | string | any} target 消息内容,如果是字符串会尝试 parse 为 JSON。
- * @return {AVMessage} 解析后的消息
- * @throws {Error} 如果不匹配任何注册的消息则抛出异常
- */
- parse(content) {
- debug$c('parsing message: %O', content); // eslint-disable-next-line
- for (const Klass of this._messageClasses) {
- const contentCopy = isPlainObject(content) ? { ...content
- } : content;
- let valid;
- let result;
- try {
- valid = Klass.validate(contentCopy);
- } catch (error) {// eslint-disable-line no-empty
- }
- if (valid) {
- try {
- result = Klass.parse(contentCopy);
- } catch (error) {
- console.warn('parsing a valid message content error', {
- error,
- Klass,
- content: contentCopy
- });
- }
- if (result !== undefined) {
- debug$c('parse result: %O', result);
- return result;
- }
- }
- }
- throw new Error('No Message Class matched');
- }
- }, (_applyDecoratedDescriptor(_class$6.prototype, "parse", [tryParseJson, applyPlugins], Object.getOwnPropertyDescriptor(_class$6.prototype, "parse"), _class$6.prototype)), _class$6);
- /** @module leancloud-realtime */
- const debug$d = d('LC:IMPlugin');
- /**
- * 消息优先级枚举
- * @enum {Number}
- * @since 3.3.0
- */
- const MessagePriority = {
- /** 高 */
- HIGH: 1,
- /** 普通 */
- NORMAL: 2,
- /** 低 */
- LOW: 3
- };
- Object.freeze(MessagePriority);
- /**
- * 为 Conversation 定义一个新属性
- * @param {String} prop 属性名
- * @param {Object} [descriptor] 属性的描述符,参见 {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor#Description getOwnPropertyDescriptor#Description - MDN},默认为该属性名对应的 Conversation 自定义属性的 getter/setter
- * @returns void
- * @example
- *
- * conversation.get('type');
- * conversation.set('type', 1);
- *
- * // equals to
- * defineConversationProperty('type');
- * conversation.type;
- * conversation.type = 1;
- */
- const defineConversationProperty = (prop, descriptor = {
- get() {
- return this.get(prop);
- },
- set(value) {
- this.set(prop, value);
- }
- }) => {
- Object.defineProperty(Conversation.prototype, prop, descriptor);
- };
- const onRealtimeCreate = realtime => {
- /* eslint-disable no-param-reassign */
- const deviceId = uuid();
- realtime._IMClients = {};
- realtime._IMClientsCreationCount = 0;
- const messageParser = new MessageParser(realtime._plugins);
- realtime._messageParser = messageParser;
- const signAVUser = async user => realtime._request({
- method: 'POST',
- path: '/rtm/sign',
- data: {
- session_token: user.getSessionToken()
- }
- });
- /**
- * 注册消息类
- *
- * 在接收消息、查询消息时,会按照消息类注册顺序的逆序依次尝试解析消息内容
- *
- * @memberof Realtime
- * @instance
- * @param {Function | Function[]} messageClass 消息类,需要实现 {@link AVMessage} 接口,
- * 建议继承自 {@link TypedMessage}
- * @throws {TypeError} 如果 messageClass 没有实现 {@link AVMessage} 接口则抛出异常
- */
- const register = messageParser.register.bind(messageParser);
- /**
- * 创建一个即时通讯客户端,多次创建相同 id 的客户端会返回同一个实例
- * @memberof Realtime
- * @instance
- * @param {String|AV.User} [identity] 客户端 identity,如果不指定该参数,服务端会随机生成一个字符串作为 identity,
- * 如果传入一个已登录的 AV.User,则会使用该用户的 id 作为客户端 identity 登录。
- * @param {Object} [options]
- * @param {Function} [options.signatureFactory] open session 时的签名方法 // TODO need details
- * @param {Function} [options.conversationSignatureFactory] 对话创建、增减成员操作时的签名方法
- * @param {Function} [options.blacklistSignatureFactory] 黑名单操作时的签名方法
- * @param {String} [options.tag] 客户端类型标记,以支持单点登录功能
- * @param {String} [options.isReconnect=false] 单点登录时标记该次登录是不是应用启动时自动重新登录
- * @return {Promise.<IMClient>}
- */
- const createIMClient = async (identity, {
- tag,
- isReconnect,
- ...clientOptions
- } = {}, lagecyTag) => {
- let id;
- const buildinOptions = {};
- if (identity) {
- if (typeof identity === 'string') {
- id = identity;
- } else if (identity.id && identity.getSessionToken) {
- ({
- id
- } = identity);
- const sessionToken = identity.getSessionToken();
- if (!sessionToken) {
- throw new Error('User must be authenticated');
- }
- buildinOptions.signatureFactory = signAVUser;
- } else {
- throw new TypeError('Identity must be a String or an AV.User');
- }
- if (realtime._IMClients[id] !== undefined) {
- return realtime._IMClients[id];
- }
- }
- if (lagecyTag) {
- console.warn('DEPRECATION createIMClient tag param: Use options.tag instead.');
- }
- const _tag = tag || lagecyTag;
- const promise = realtime._open().then(connection => {
- const client = new IMClient(id, { ...buildinOptions,
- ...clientOptions
- }, {
- _connection: connection,
- _request: realtime._request.bind(realtime),
- _messageParser: messageParser,
- _plugins: realtime._plugins,
- _identity: identity
- });
- connection.on(RECONNECT, () => client._open(realtime._options.appId, _tag, deviceId, true)
- /**
- * 客户端连接恢复正常,该事件通常在 {@link Realtime#event:RECONNECT} 之后发生
- * @event IMClient#RECONNECT
- * @see Realtime#event:RECONNECT
- * @since 3.2.0
- */
- /**
- * 客户端重新登录发生错误(网络连接已恢复,但重新登录错误)
- * @event IMClient#RECONNECT_ERROR
- * @since 3.2.0
- */
- .then(() => client.emit(RECONNECT), error => client.emit(RECONNECT_ERROR, error)));
- internal(client)._eventemitter.on('beforeclose', () => {
- delete realtime._IMClients[client.id];
- if (realtime._firstIMClient === client) {
- delete realtime._firstIMClient;
- }
- }, realtime);
- internal(client)._eventemitter.on('close', () => {
- realtime._deregister(client);
- }, realtime);
- return client._open(realtime._options.appId, _tag, deviceId, isReconnect).then(() => {
- realtime._IMClients[client.id] = client;
- realtime._IMClientsCreationCount += 1;
- if (realtime._IMClientsCreationCount === 1) {
- client._omitPeerId(true);
- realtime._firstIMClient = client;
- } else if (realtime._IMClientsCreationCount > 1 && realtime._firstIMClient) {
- realtime._firstIMClient._omitPeerId(false);
- }
- realtime._register(client);
- return client;
- }).catch(error => {
- delete realtime._IMClients[client.id];
- throw error;
- });
- }).then(...finalize(() => {
- realtime._deregisterPending(promise);
- })).catch(error => {
- delete realtime._IMClients[id];
- throw error;
- });
- if (identity) {
- realtime._IMClients[id] = promise;
- }
- realtime._registerPending(promise);
- return promise;
- };
- Object.assign(realtime, {
- register,
- createIMClient
- });
- /* eslint-enable no-param-reassign */
- };
- const beforeCommandDispatch = (command, realtime) => {
- const isIMCommand = command.service === null || command.service === 2;
- if (!isIMCommand) return true;
- const targetClient = command.peerId ? realtime._IMClients[command.peerId] : realtime._firstIMClient;
- if (targetClient) {
- Promise.resolve(targetClient).then(client => client._dispatchCommand(command)).catch(debug$d);
- } else {
- debug$d('[WARN] Unexpected message received without any live client match: %O', trim(command));
- }
- return false;
- };
- const IMPlugin = {
- name: 'leancloud-realtime-plugin-im',
- onRealtimeCreate,
- beforeCommandDispatch,
- messageClasses: [Message, BinaryMessage, RecalledMessage, TextMessage]
- };
- /** @module leancloud-realtime */
- Realtime.defineConversationProperty = defineConversationProperty;
- Realtime.__preRegisteredPlugins = [IMPlugin];
- const Event = { ...CoreEvent,
- ...IMEvent
- };
- /** core + plugins + platform adapters */
- setAdapters({
- WebSocket: platformAdaptersNode.WebSocket,
- request: platformAdaptersNode.request
- });
- exports.EventEmitter = EventEmitter;
- exports.BinaryMessage = BinaryMessage;
- exports.ChatRoom = ChatRoom;
- exports.Conversation = Conversation;
- exports.ConversationMemberRole = ConversationMemberRole;
- exports.ConversationQuery = ConversationQuery;
- exports.ErrorCode = ErrorCode;
- exports.Event = Event;
- exports.IE10Compatible = IE10Compatible;
- exports.IMPlugin = IMPlugin;
- exports.Message = Message;
- exports.MessageParser = MessageParser;
- exports.MessagePriority = MessagePriority;
- exports.MessageQueryDirection = MessageQueryDirection;
- exports.MessageStatus = MessageStatus;
- exports.Promise = polyfilledPromise;
- exports.Protocals = message;
- exports.Protocols = message;
- exports.Realtime = Realtime;
- exports.RecalledMessage = RecalledMessage;
- exports.ServiceConversation = ServiceConversation;
- exports.TemporaryConversation = TemporaryConversation;
- exports.TextMessage = TextMessage;
- exports.TypedMessage = TypedMessage;
- exports.debug = debug$2;
- exports.defineConversationProperty = defineConversationProperty;
- exports.getAdapter = getAdapter;
- exports.messageField = messageField;
- exports.messageType = messageType;
- exports.setAdapters = setAdapters;
- //# sourceMappingURL=im-node.js.map
|