single-file-extension-background.js 216 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496
  1. (function () {
  2. 'use strict';
  3. /*
  4. * Copyright 2010-2020 Gildas Lormeau
  5. * contact : gildas.lormeau <at> gmail.com
  6. *
  7. * This file is part of SingleFile.
  8. *
  9. * The code in this file is free software: you can redistribute it and/or
  10. * modify it under the terms of the GNU Affero General Public License
  11. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  12. * of the License, or (at your option) any later version.
  13. *
  14. * The code in this file is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  17. * General Public License for more details.
  18. *
  19. * As additional permission under GNU AGPL version 3 section 7, you may
  20. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  21. * AGPL normally required by section 4, provided you include this license
  22. * notice and a URL through which recipients can access the Corresponding
  23. * Source.
  24. */
  25. /* global browser */
  26. const STATE_DOWNLOAD_COMPLETE = "complete";
  27. const STATE_DOWNLOAD_INTERRUPTED = "interrupted";
  28. const STATE_ERROR_CANCELED_CHROMIUM = "USER_CANCELED";
  29. const ERROR_DOWNLOAD_CANCELED_GECKO = "canceled";
  30. const ERROR_CONFLICT_ACTION_GECKO = "conflictaction prompt not yet implemented";
  31. const ERROR_INCOGNITO_GECKO = "'incognito'";
  32. const ERROR_INCOGNITO_GECKO_ALT = "\"incognito\"";
  33. const ERROR_INVALID_FILENAME_GECKO = "illegal characters";
  34. const ERROR_INVALID_FILENAME_CHROMIUM = "invalid filename";
  35. async function download(downloadInfo, replacementCharacter) {
  36. let downloadId;
  37. try {
  38. downloadId = await browser.downloads.download(downloadInfo);
  39. } catch (error) {
  40. if (error.message) {
  41. const errorMessage = error.message.toLowerCase();
  42. const invalidFilename = errorMessage.includes(ERROR_INVALID_FILENAME_GECKO) || errorMessage.includes(ERROR_INVALID_FILENAME_CHROMIUM);
  43. if (invalidFilename && downloadInfo.filename.startsWith(".")) {
  44. downloadInfo.filename = replacementCharacter + downloadInfo.filename;
  45. return download(downloadInfo, replacementCharacter);
  46. } else if (invalidFilename && downloadInfo.filename.includes(",")) {
  47. downloadInfo.filename = downloadInfo.filename.replace(/,/g, replacementCharacter);
  48. return download(downloadInfo, replacementCharacter);
  49. } else if (invalidFilename && downloadInfo.filename.match(/\u200C|\u200D|\u200E|\u200F/)) {
  50. downloadInfo.filename = downloadInfo.filename.replace(/\u200C|\u200D|\u200E|\u200F/g, replacementCharacter);
  51. return download(downloadInfo, replacementCharacter);
  52. } else if (invalidFilename && !downloadInfo.filename.match(/^[\x00-\x7F]+$/)) { // eslint-disable-line no-control-regex
  53. downloadInfo.filename = downloadInfo.filename.replace(/[^\x00-\x7F]+/g, replacementCharacter); // eslint-disable-line no-control-regex
  54. return download(downloadInfo, replacementCharacter);
  55. } else if ((errorMessage.includes(ERROR_INCOGNITO_GECKO) || errorMessage.includes(ERROR_INCOGNITO_GECKO_ALT)) && downloadInfo.incognito) {
  56. delete downloadInfo.incognito;
  57. return download(downloadInfo, replacementCharacter);
  58. } else if (errorMessage == ERROR_CONFLICT_ACTION_GECKO && downloadInfo.conflictAction) {
  59. delete downloadInfo.conflictAction;
  60. return download(downloadInfo, replacementCharacter);
  61. } else if (errorMessage.includes(ERROR_DOWNLOAD_CANCELED_GECKO)) {
  62. return {};
  63. } else {
  64. throw error;
  65. }
  66. } else {
  67. throw error;
  68. }
  69. }
  70. return new Promise((resolve, reject) => {
  71. browser.downloads.onChanged.addListener(onChanged);
  72. function onChanged(event) {
  73. if (event.id == downloadId && event.state) {
  74. if (event.state.current == STATE_DOWNLOAD_COMPLETE) {
  75. browser.downloads.search({ id: downloadId })
  76. .then(downloadItems => resolve({ filename: downloadItems[0] && downloadItems[0].filename }))
  77. .catch(() => resolve({}));
  78. browser.downloads.onChanged.removeListener(onChanged);
  79. }
  80. if (event.state.current == STATE_DOWNLOAD_INTERRUPTED) {
  81. if (event.error && event.error.current == STATE_ERROR_CANCELED_CHROMIUM) {
  82. resolve({});
  83. } else {
  84. reject(new Error(event.state.current));
  85. }
  86. browser.downloads.onChanged.removeListener(onChanged);
  87. }
  88. }
  89. }
  90. });
  91. }
  92. /*
  93. * Copyright 2010-2020 Gildas Lormeau
  94. * contact : gildas.lormeau <at> gmail.com
  95. *
  96. * This file is part of SingleFile.
  97. *
  98. * The code in this file is free software: you can redistribute it and/or
  99. * modify it under the terms of the GNU Affero General Public License
  100. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  101. * of the License, or (at your option) any later version.
  102. *
  103. * The code in this file is distributed in the hope that it will be useful,
  104. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  105. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  106. * General Public License for more details.
  107. *
  108. * As additional permission under GNU AGPL version 3 section 7, you may
  109. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  110. * AGPL normally required by section 4, provided you include this license
  111. * notice and a URL through which recipients can access the Corresponding
  112. * Source.
  113. */
  114. /* global browser, setTimeout */
  115. let persistentData, temporaryData, cleanedUp;
  116. setTimeout(() => getPersistent().then(tabsData => persistentData = tabsData), 0);
  117. function onMessage$e(message) {
  118. if (message.method.endsWith(".get")) {
  119. return getPersistent();
  120. }
  121. if (message.method.endsWith(".set")) {
  122. return setPersistent(message.tabsData);
  123. }
  124. }
  125. async function onTabReplaced$3(addedTabId, removedTabId) {
  126. let tabsData = await getPersistent();
  127. await updateTabsData(tabsData, addedTabId, removedTabId);
  128. setPersistent(tabsData);
  129. await updateTabsData(temporaryData, addedTabId, removedTabId);
  130. }
  131. async function updateTabsData(tabsData, addedTabId, removedTabId) {
  132. if (tabsData[removedTabId] && !tabsData[addedTabId]) {
  133. tabsData[addedTabId] = tabsData[removedTabId];
  134. delete tabsData[removedTabId];
  135. }
  136. }
  137. async function remove(tabId) {
  138. if (temporaryData) {
  139. delete temporaryData[tabId];
  140. }
  141. const tabsData = await getPersistent();
  142. if (tabsData[tabId]) {
  143. const autoSave = tabsData[tabId].autoSave;
  144. tabsData[tabId] = { autoSave };
  145. await setPersistent(tabsData);
  146. }
  147. }
  148. function getTemporary(desiredTabId) {
  149. if (!temporaryData) {
  150. temporaryData = {};
  151. }
  152. if (desiredTabId !== undefined && !temporaryData[desiredTabId]) {
  153. temporaryData[desiredTabId] = {};
  154. }
  155. return temporaryData;
  156. }
  157. async function getPersistent(desiredTabId) {
  158. if (!persistentData) {
  159. const config = await browser.storage.local.get();
  160. persistentData = config.tabsData || {};
  161. }
  162. cleanup();
  163. if (desiredTabId !== undefined && !persistentData[desiredTabId]) {
  164. persistentData[desiredTabId] = {};
  165. }
  166. return persistentData;
  167. }
  168. async function setPersistent(tabsData) {
  169. persistentData = tabsData;
  170. await browser.storage.local.set({ tabsData });
  171. }
  172. async function cleanup() {
  173. if (!cleanedUp) {
  174. cleanedUp = true;
  175. const tabs = await browser.tabs.query({ currentWindow: true, highlighted: true });
  176. Object.keys(persistentData).filter(key => {
  177. if (key != "autoSaveAll" && key != "autoSaveUnpinned" && key != "profileName") {
  178. return !tabs.find(tab => tab.id == key);
  179. }
  180. }).forEach(tabId => delete persistentData[tabId]);
  181. await browser.storage.local.set({ tabsData: persistentData });
  182. }
  183. }
  184. /*
  185. * Copyright 2010-2020 Gildas Lormeau
  186. * contact : gildas.lormeau <at> gmail.com
  187. *
  188. * This file is part of SingleFile.
  189. *
  190. * The code in this file is free software: you can redistribute it and/or
  191. * modify it under the terms of the GNU Affero General Public License
  192. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  193. * of the License, or (at your option) any later version.
  194. *
  195. * The code in this file is distributed in the hope that it will be useful,
  196. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  197. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  198. * General Public License for more details.
  199. *
  200. * As additional permission under GNU AGPL version 3 section 7, you may
  201. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  202. * AGPL normally required by section 4, provided you include this license
  203. * notice and a URL through which recipients can access the Corresponding
  204. * Source.
  205. */
  206. const CURRENT_PROFILE_NAME = "-";
  207. const DEFAULT_PROFILE_NAME = "__Default_Settings__";
  208. const DISABLED_PROFILE_NAME = "__Disabled_Settings__";
  209. const REGEXP_RULE_PREFIX = "regexp:";
  210. const PROFILE_NAME_PREFIX = "profile_";
  211. const IS_NOT_SAFARI = !/Safari/.test(navigator.userAgent) || /Chrome/.test(navigator.userAgent) || /Vivaldi/.test(navigator.userAgent) || /OPR/.test(navigator.userAgent);
  212. const BACKGROUND_SAVE_SUPPORTED = !(/Mobile.*Firefox/.test(navigator.userAgent) || /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent) && !/Vivaldi/.test(navigator.userAgent) && !/OPR/.test(navigator.userAgent));
  213. const BADGE_COLOR_SUPPORTED = IS_NOT_SAFARI;
  214. const AUTO_SAVE_SUPPORTED = IS_NOT_SAFARI;
  215. const SELECTABLE_TABS_SUPPORTED = IS_NOT_SAFARI;
  216. const AUTO_OPEN_EDITOR_SUPPORTED = IS_NOT_SAFARI;
  217. const INFOBAR_SUPPORTED = IS_NOT_SAFARI;
  218. const BOOKMARKS_API_SUPPORTED = IS_NOT_SAFARI;
  219. const IDENTITY_API_SUPPORTED = IS_NOT_SAFARI;
  220. const CLIPBOARD_API_SUPPORTED = IS_NOT_SAFARI;
  221. const NATIVE_API_API_SUPPORTED = IS_NOT_SAFARI;
  222. const WEB_BLOCKING_API_SUPPORTED = IS_NOT_SAFARI;
  223. const SHARE_API_SUPPORTED = navigator.canShare && navigator.canShare({ files: [new File([new Blob([""], { type: "text/html" })], "test.html")] });
  224. const DEFAULT_CONFIG = {
  225. removeHiddenElements: true,
  226. removeUnusedStyles: true,
  227. removeUnusedFonts: true,
  228. removeFrames: false,
  229. compressHTML: true,
  230. compressCSS: false,
  231. loadDeferredImages: true,
  232. loadDeferredImagesMaxIdleTime: 1500,
  233. loadDeferredImagesBlockCookies: false,
  234. loadDeferredImagesBlockStorage: false,
  235. loadDeferredImagesKeepZoomLevel: false,
  236. loadDeferredImagesDispatchScrollEvent: false,
  237. loadDeferredImagesBeforeFrames: false,
  238. filenameTemplate: "%if-empty<{page-title}|No title> ({date-locale} {time-locale}).{filename-extension}",
  239. infobarTemplate: "",
  240. includeInfobar: !IS_NOT_SAFARI,
  241. confirmInfobarContent: false,
  242. autoClose: false,
  243. confirmFilename: false,
  244. filenameConflictAction: "uniquify",
  245. filenameMaxLength: 192,
  246. filenameMaxLengthUnit: "bytes",
  247. filenameReplacedCharacters: ["~", "+", "\\\\", "?", "%", "*", ":", "|", "\"", "<", ">", "\x00-\x1f", "\x7F"],
  248. filenameReplacementCharacter: "_",
  249. replaceEmojisInFilename: false,
  250. saveFilenameTemplateData: false,
  251. contextMenuEnabled: true,
  252. tabMenuEnabled: true,
  253. browserActionMenuEnabled: true,
  254. shadowEnabled: true,
  255. logsEnabled: true,
  256. progressBarEnabled: true,
  257. maxResourceSizeEnabled: false,
  258. maxResourceSize: 10,
  259. displayInfobar: true,
  260. displayStats: false,
  261. backgroundSave: BACKGROUND_SAVE_SUPPORTED,
  262. defaultEditorMode: "normal",
  263. applySystemTheme: true,
  264. autoSaveDelay: 1,
  265. autoSaveLoad: false,
  266. autoSaveUnload: false,
  267. autoSaveLoadOrUnload: true,
  268. autoSaveDiscard: false,
  269. autoSaveRemove: false,
  270. autoSaveRepeat: false,
  271. autoSaveRepeatDelay: 10,
  272. removeAlternativeFonts: true,
  273. removeAlternativeMedias: true,
  274. removeAlternativeImages: true,
  275. groupDuplicateImages: true,
  276. maxSizeDuplicateImages: 512 * 1024,
  277. saveRawPage: false,
  278. saveToClipboard: false,
  279. addProof: false,
  280. saveToGDrive: false,
  281. saveToDropbox: false,
  282. saveWithWebDAV: false,
  283. webDAVURL: "",
  284. webDAVUser: "",
  285. webDAVPassword: "",
  286. saveToGitHub: false,
  287. saveToRestFormApi: false,
  288. githubToken: "",
  289. githubUser: "",
  290. githubRepository: "SingleFile-Archives",
  291. githubBranch: "main",
  292. saveWithCompanion: false,
  293. sharePage: false,
  294. forceWebAuthFlow: false,
  295. resolveFragmentIdentifierURLs: false,
  296. userScriptEnabled: false,
  297. openEditor: false,
  298. openSavedPage: false,
  299. autoOpenEditor: false,
  300. saveCreatedBookmarks: false,
  301. allowedBookmarkFolders: [],
  302. ignoredBookmarkFolders: [],
  303. replaceBookmarkURL: true,
  304. saveFavicon: true,
  305. includeBOM: false,
  306. warnUnsavedPage: true,
  307. displayInfobarInEditor: false,
  308. compressContent: false,
  309. createRootDirectory: false,
  310. selfExtractingArchive: true,
  311. extractDataFromPage: true,
  312. preventAppendedData: false,
  313. insertEmbeddedImage: false,
  314. insertEmbeddedScreenshotImage: false,
  315. insertTextBody: false,
  316. autoSaveExternalSave: false,
  317. insertMetaNoIndex: false,
  318. insertMetaCSP: true,
  319. passReferrerOnError: false,
  320. password: "",
  321. insertSingleFileComment: true,
  322. removeSavedDate: false,
  323. blockMixedContent: false,
  324. saveOriginalURLs: false,
  325. acceptHeaders: {
  326. font: "application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8",
  327. image: "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8",
  328. stylesheet: "text/css,*/*;q=0.1",
  329. script: "*/*",
  330. document: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  331. video: "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
  332. audio: "audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5"
  333. },
  334. moveStylesInHead: false,
  335. networkTimeout: 0,
  336. woleetKey: "",
  337. blockImages: false,
  338. blockStylesheets: false,
  339. blockFonts: false,
  340. blockScripts: true,
  341. blockVideos: true,
  342. blockAudios: true,
  343. delayBeforeProcessing: 0,
  344. _migratedTemplateFormat: true,
  345. saveToRestFormApiUrl: "",
  346. saveToRestFormApiFileFieldName: "",
  347. saveToRestFormApiUrlFieldName: "",
  348. saveToRestFormApiToken: "",
  349. };
  350. const DEFAULT_RULES = [{
  351. "url": "file:",
  352. "profile": "__Default_Settings__",
  353. "autoSaveProfile": "__Disabled_Settings__"
  354. }];
  355. const MIGRATION_DEFAULT_VARIABLES_VALUES = {
  356. "page-title": "No title",
  357. "page-heading": "No heading",
  358. "page-language": "No language",
  359. "page-description": "No description",
  360. "page-author": "No author",
  361. "page-creator": "No creator",
  362. "page-publisher": "No publisher",
  363. "url-hash": "No hash",
  364. "url-host": "No host",
  365. "url-hostname": "No hostname",
  366. "url-href": "No href",
  367. "url-href-digest-sha-1": "No hash",
  368. "url-href-flat": "No href",
  369. "url-referrer": "No referrer",
  370. "url-referrer-flat": "No referrer",
  371. "url-password": "No password",
  372. "url-pathname": "No pathname",
  373. "url-pathname-flat": "No pathname",
  374. "url-port": "No port",
  375. "url-protocol": "No protocol",
  376. "url-search": "No search",
  377. "url-username": "No username",
  378. "tab-id": "No tab id",
  379. "tab-index": "No tab index",
  380. "url-last-segment": "No last segment"
  381. };
  382. let configStorage;
  383. let pendingUpgradePromise = upgrade();
  384. async function upgrade() {
  385. const { sync } = await browser.storage.local.get();
  386. if (sync) {
  387. configStorage = browser.storage.sync;
  388. } else {
  389. configStorage = browser.storage.local;
  390. }
  391. const config = await configStorage.get();
  392. if (!config[PROFILE_NAME_PREFIX + DEFAULT_PROFILE_NAME]) {
  393. if (config.profiles) {
  394. const profileNames = Object.keys(config.profiles);
  395. for (const profileName of profileNames) {
  396. await setProfile(profileName, config.profiles[profileName]);
  397. }
  398. } else {
  399. await setProfile(DEFAULT_PROFILE_NAME, DEFAULT_CONFIG);
  400. }
  401. } else if (config.profiles) {
  402. await configStorage.remove(["profiles"]);
  403. }
  404. if (!config.rules) {
  405. await configStorage.set({ rules: DEFAULT_RULES });
  406. }
  407. if (!config.maxParallelWorkers) {
  408. await configStorage.set({ maxParallelWorkers: navigator.hardwareConcurrency || 4 });
  409. }
  410. if (!config.processInForeground) {
  411. await configStorage.set({ processInForeground: false });
  412. }
  413. const profileNames = await getProfileNames();
  414. profileNames.map(async profileName => {
  415. const profile = await getProfile(profileName);
  416. if (!profile._migratedTemplateFormat) {
  417. profile.filenameTemplate = updateFilenameTemplate(profile.filenameTemplate);
  418. profile._migratedTemplateFormat = true;
  419. }
  420. for (const key of Object.keys(DEFAULT_CONFIG)) {
  421. if (profile[key] === undefined) {
  422. profile[key] = DEFAULT_CONFIG[key];
  423. }
  424. }
  425. await setProfile(profileName, profile);
  426. });
  427. }
  428. function updateFilenameTemplate(template) {
  429. try {
  430. Object.keys(MIGRATION_DEFAULT_VARIABLES_VALUES).forEach(variable => {
  431. const value = MIGRATION_DEFAULT_VARIABLES_VALUES[variable];
  432. template = template.replaceAll(`{${variable}}`, `%if-empty<{${variable}}|${value}>`);
  433. });
  434. return template;
  435. } catch (_error) {
  436. // ignored
  437. }
  438. }
  439. async function getRule(url, ignoreWildcard) {
  440. const { rules } = await configStorage.get(["rules"]);
  441. const regExpRules = rules.filter(rule => testRegExpRule(rule));
  442. let rule = regExpRules.sort(sortRules).find(rule => url && url.match(new RegExp(rule.url.split(REGEXP_RULE_PREFIX)[1])));
  443. if (!rule) {
  444. const normalRules = rules.filter(rule => !testRegExpRule(rule));
  445. rule = normalRules.sort(sortRules).find(rule => (!ignoreWildcard && rule.url == "*") || (url && url.includes(rule.url)));
  446. }
  447. return rule;
  448. }
  449. async function getConfig() {
  450. await pendingUpgradePromise;
  451. const { maxParallelWorkers, processInForeground } = await configStorage.get(["maxParallelWorkers", "processInForeground"]);
  452. const rules = await getRules();
  453. const profiles = await getProfiles();
  454. return { profiles, rules, maxParallelWorkers, processInForeground };
  455. }
  456. function sortRules(ruleLeft, ruleRight) {
  457. return ruleRight.url.length - ruleLeft.url.length;
  458. }
  459. function testRegExpRule(rule) {
  460. return rule.url.toLowerCase().startsWith(REGEXP_RULE_PREFIX);
  461. }
  462. async function onMessage$d(message) {
  463. if (message.method.endsWith(".deleteRules")) {
  464. await deleteRules(message.profileName);
  465. }
  466. if (message.method.endsWith(".deleteRule")) {
  467. await deleteRule(message.url);
  468. }
  469. if (message.method.endsWith(".addRule")) {
  470. await addRule(message.url, message.profileName, message.autoSaveProfileName);
  471. }
  472. if (message.method.endsWith(".createProfile")) {
  473. await createProfile(message.profileName, message.fromProfileName || DEFAULT_PROFILE_NAME);
  474. }
  475. if (message.method.endsWith(".renameProfile")) {
  476. await renameProfile(message.profileName, message.newProfileName);
  477. }
  478. if (message.method.endsWith(".deleteProfile")) {
  479. await deleteProfile(message.profileName);
  480. }
  481. if (message.method.endsWith(".resetProfiles")) {
  482. await resetProfiles();
  483. }
  484. if (message.method.endsWith(".resetProfile")) {
  485. await resetProfile(message.profileName);
  486. }
  487. if (message.method.endsWith(".importConfig")) {
  488. await importConfig(message.config);
  489. }
  490. if (message.method.endsWith(".updateProfile")) {
  491. await updateProfile(message.profileName, message.profile);
  492. }
  493. if (message.method.endsWith(".updateRule")) {
  494. await updateRule(message.url, message.newUrl, message.profileName, message.autoSaveProfileName);
  495. }
  496. if (message.method.endsWith(".getConstants")) {
  497. return {
  498. DISABLED_PROFILE_NAME,
  499. DEFAULT_PROFILE_NAME,
  500. CURRENT_PROFILE_NAME,
  501. BACKGROUND_SAVE_SUPPORTED,
  502. BADGE_COLOR_SUPPORTED,
  503. AUTO_SAVE_SUPPORTED,
  504. SELECTABLE_TABS_SUPPORTED,
  505. AUTO_OPEN_EDITOR_SUPPORTED,
  506. INFOBAR_SUPPORTED,
  507. BOOKMARKS_API_SUPPORTED,
  508. IDENTITY_API_SUPPORTED,
  509. CLIPBOARD_API_SUPPORTED,
  510. NATIVE_API_API_SUPPORTED,
  511. WEB_BLOCKING_API_SUPPORTED,
  512. SHARE_API_SUPPORTED
  513. };
  514. }
  515. if (message.method.endsWith(".getRules")) {
  516. return getRules();
  517. }
  518. if (message.method.endsWith(".getProfiles")) {
  519. return getProfiles();
  520. }
  521. if (message.method.endsWith(".exportConfig")) {
  522. return exportConfig();
  523. }
  524. if (message.method.endsWith(".enableSync")) {
  525. await browser.storage.local.set({ sync: true });
  526. const syncConfig = await browser.storage.sync.get();
  527. if (!syncConfig || !syncConfig.rules) {
  528. const profileKeyNames = await getProfileKeyNames();
  529. const localConfig = await browser.storage.local.get(["rules", "maxParallelWorkers", "processInForeground", ...profileKeyNames]);
  530. await browser.storage.sync.set(localConfig);
  531. }
  532. configStorage = browser.storage.sync;
  533. await upgrade();
  534. return {};
  535. }
  536. if (message.method.endsWith(".disableSync")) {
  537. await browser.storage.local.set({ sync: false });
  538. const syncConfig = await browser.storage.sync.get();
  539. const localConfig = await browser.storage.local.get();
  540. if (syncConfig && syncConfig.rules && (!localConfig || !localConfig.rules)) {
  541. await browser.storage.local.set({ rules: syncConfig.rules, maxParallelWorkers: syncConfig.maxParallelWorkers, processInForeground: syncConfig.processInForeground });
  542. const profiles = {};
  543. // syncConfig.profileNames.forEach(profileKeyName => profiles[PROFILE_NAME_PREFIX + profileKeyName] = syncConfig[profileKeyName]);
  544. await browser.storage.local.set(profiles);
  545. }
  546. configStorage = browser.storage.local;
  547. await upgrade();
  548. return {};
  549. }
  550. if (message.method.endsWith(".isSync")) {
  551. return { sync: (await browser.storage.local.get()).sync };
  552. }
  553. return {};
  554. }
  555. async function createProfile(profileName, fromProfileName) {
  556. const profileNames = await getProfileNames();
  557. if (profileNames.includes(profileName)) {
  558. throw new Error("Duplicate profile name");
  559. }
  560. const profileFrom = await getProfile(fromProfileName);
  561. const profile = JSON.parse(JSON.stringify(profileFrom));
  562. await setProfile(profileName, profile);
  563. }
  564. async function getProfiles() {
  565. await pendingUpgradePromise;
  566. const profileKeyNames = await getProfileKeyNames();
  567. const profiles = await configStorage.get(profileKeyNames);
  568. const result = {};
  569. Object.keys(profiles).forEach(profileName => result[profileName.substring(PROFILE_NAME_PREFIX.length)] = profiles[profileName]);
  570. return result;
  571. }
  572. async function getOptions(url, autoSave) {
  573. await pendingUpgradePromise;
  574. const [rule, allTabsData] = await Promise.all([getRule(url), getPersistent()]);
  575. const tabProfileName = allTabsData.profileName || DEFAULT_PROFILE_NAME;
  576. let selectedProfileName;
  577. if (rule) {
  578. const profileName = rule[autoSave ? "autoSaveProfile" : "profile"];
  579. selectedProfileName = profileName == CURRENT_PROFILE_NAME ? tabProfileName : profileName;
  580. } else {
  581. selectedProfileName = tabProfileName;
  582. }
  583. const profile = await getProfile(selectedProfileName);
  584. return Object.assign({ profileName: selectedProfileName }, profile);
  585. }
  586. async function updateProfile(profileName, profile) {
  587. const profileNames = await getProfileNames();
  588. if (!profileNames.includes(profileName)) {
  589. throw new Error("Profile not found");
  590. }
  591. const previousProfile = await getProfile(profileName);
  592. Object.keys(previousProfile).forEach(key => {
  593. profile[key] = profile[key] === undefined ? previousProfile[key] : profile[key];
  594. });
  595. await setProfile(profileName, profile);
  596. }
  597. async function renameProfile(oldProfileName, profileName) {
  598. const profileNames = await getProfileNames();
  599. const allTabsData = await getPersistent();
  600. const rules = await getRules();
  601. if (!profileNames.includes(oldProfileName)) {
  602. throw new Error("Profile not found");
  603. }
  604. if (profileNames.includes(profileName)) {
  605. throw new Error("Duplicate profile name");
  606. }
  607. if (oldProfileName == DEFAULT_PROFILE_NAME) {
  608. throw new Error("Default settings cannot be renamed");
  609. }
  610. if (allTabsData.profileName == oldProfileName) {
  611. allTabsData.profileName = profileName;
  612. await setPersistent(allTabsData);
  613. }
  614. rules.forEach(rule => {
  615. if (rule.profile == oldProfileName) {
  616. rule.profile = profileName;
  617. }
  618. if (rule.autoSaveProfile == oldProfileName) {
  619. rule.autoSaveProfile = profileName;
  620. }
  621. });
  622. const profile = await getProfile(oldProfileName);
  623. await configStorage.remove([PROFILE_NAME_PREFIX + oldProfileName]);
  624. await configStorage.set({ [PROFILE_NAME_PREFIX + profileName]: profile, rules });
  625. }
  626. async function deleteProfile(profileName) {
  627. const profileNames = await getProfileNames();
  628. const allTabsData = await getPersistent();
  629. const rules = await getRules();
  630. if (!profileNames.includes(profileName)) {
  631. throw new Error("Profile not found");
  632. }
  633. if (profileName == DEFAULT_PROFILE_NAME) {
  634. throw new Error("Default settings cannot be deleted");
  635. }
  636. if (allTabsData.profileName == profileName) {
  637. delete allTabsData.profileName;
  638. await setPersistent(allTabsData);
  639. }
  640. rules.forEach(rule => {
  641. if (rule.profile == profileName) {
  642. rule.profile = DEFAULT_PROFILE_NAME;
  643. }
  644. if (rule.autoSaveProfile == profileName) {
  645. rule.autoSaveProfile = DEFAULT_PROFILE_NAME;
  646. }
  647. });
  648. configStorage.remove([PROFILE_NAME_PREFIX + profileName]);
  649. await configStorage.set({ rules });
  650. }
  651. async function getRules() {
  652. return (await configStorage.get(["rules"])).rules;
  653. }
  654. async function getProfileNames() {
  655. return Object.keys(await configStorage.get()).filter(key => key.startsWith(PROFILE_NAME_PREFIX)).map(key => key.substring(PROFILE_NAME_PREFIX.length));
  656. }
  657. async function getProfileKeyNames() {
  658. return Object.keys(await configStorage.get()).filter(key => key.startsWith(PROFILE_NAME_PREFIX));
  659. }
  660. async function getProfile(profileName) {
  661. const profileKey = PROFILE_NAME_PREFIX + profileName;
  662. const data = await configStorage.get([profileKey]);
  663. return data[profileKey];
  664. }
  665. async function setProfile(profileName, profileData) {
  666. const profileKey = PROFILE_NAME_PREFIX + profileName;
  667. await configStorage.set({ [profileKey]: profileData });
  668. }
  669. async function addRule(url, profile, autoSaveProfile) {
  670. if (!url) {
  671. throw new Error("URL is empty");
  672. }
  673. const rules = await getRules();
  674. if (rules.find(rule => rule.url == url)) {
  675. throw new Error("URL already exists");
  676. }
  677. rules.push({
  678. url,
  679. profile,
  680. autoSaveProfile
  681. });
  682. await configStorage.set({ rules });
  683. }
  684. async function deleteRule(url) {
  685. if (!url) {
  686. throw new Error("URL is empty");
  687. }
  688. const rules = await getRules();
  689. await configStorage.set({ rules: rules.filter(rule => rule.url != url) });
  690. }
  691. async function deleteRules(profileName) {
  692. const rules = await getRules();
  693. await configStorage.set({ rules: profileName ? rules.filter(rule => rule.autoSaveProfile != profileName && rule.profile != profileName) : [] });
  694. }
  695. async function updateRule(url, newURL, profile, autoSaveProfile) {
  696. if (!url || !newURL) {
  697. throw new Error("URL is empty");
  698. }
  699. const rules = await getRules();
  700. const urlConfig = rules.find(rule => rule.url == url);
  701. if (!urlConfig) {
  702. throw new Error("URL not found");
  703. }
  704. if (rules.find(rule => rule.url == newURL && rule.url != url)) {
  705. throw new Error("New URL already exists");
  706. }
  707. urlConfig.url = newURL;
  708. urlConfig.profile = profile;
  709. urlConfig.autoSaveProfile = autoSaveProfile;
  710. await configStorage.set({ rules });
  711. }
  712. async function getAuthInfo$1() {
  713. return (await configStorage.get()).authInfo;
  714. }
  715. async function getDropboxAuthInfo$1() {
  716. return (await configStorage.get()).dropboxAuthInfo;
  717. }
  718. async function setAuthInfo(authInfo) {
  719. await configStorage.set({ authInfo });
  720. }
  721. async function setDropboxAuthInfo(authInfo) {
  722. await configStorage.set({ dropboxAuthInfo: authInfo });
  723. }
  724. async function removeAuthInfo() {
  725. let authInfo = getAuthInfo$1();
  726. if (authInfo.revokableAccessToken) {
  727. setAuthInfo({ revokableAccessToken: authInfo.revokableAccessToken });
  728. } else {
  729. await configStorage.remove(["authInfo"]);
  730. }
  731. }
  732. async function removeDropboxAuthInfo() {
  733. let authInfo = getDropboxAuthInfo$1();
  734. if (authInfo.revokableAccessToken) {
  735. setDropboxAuthInfo({ revokableAccessToken: authInfo.revokableAccessToken });
  736. } else {
  737. await configStorage.remove(["dropboxAuthInfo"]);
  738. }
  739. }
  740. async function resetProfiles() {
  741. await pendingUpgradePromise;
  742. const allTabsData = await getPersistent();
  743. delete allTabsData.profileName;
  744. await setPersistent(allTabsData);
  745. let profileKeyNames = await getProfileKeyNames();
  746. await configStorage.remove([...profileKeyNames, "rules", "maxParallelWorkers", "processInForeground"]);
  747. await upgrade();
  748. }
  749. async function resetProfile(profileName) {
  750. const profileNames = await getProfileNames();
  751. if (!profileNames.includes(profileName)) {
  752. throw new Error("Profile not found");
  753. }
  754. await setProfile(profileName, DEFAULT_CONFIG);
  755. }
  756. async function exportConfig() {
  757. const config = await getConfig();
  758. const textContent = JSON.stringify({ profiles: config.profiles, rules: config.rules, maxParallelWorkers: config.maxParallelWorkers, processInForeground: config.processInForeground }, null, 2);
  759. const filename = `singlefile-settings-${(new Date()).toISOString().replace(/:/g, "_")}.json`;
  760. if (BACKGROUND_SAVE_SUPPORTED) {
  761. const url = URL.createObjectURL(new Blob([textContent], { type: "text/json" }));
  762. try {
  763. await download({
  764. url,
  765. filename,
  766. saveAs: true
  767. }, "_");
  768. } finally {
  769. URL.revokeObjectURL(url);
  770. }
  771. return {};
  772. } else {
  773. return {
  774. filename,
  775. textContent
  776. };
  777. }
  778. }
  779. async function importConfig(config) {
  780. const profileNames = await getProfileNames();
  781. const profileKeyNames = await getProfileKeyNames();
  782. const allTabsData = await getPersistent();
  783. if (profileNames.includes(allTabsData.profileName)) {
  784. delete allTabsData.profileName;
  785. await setPersistent(allTabsData);
  786. }
  787. await configStorage.remove([...profileKeyNames, "rules", "maxParallelWorkers", "processInForeground"]);
  788. const newConfig = { rules: config.rules, maxParallelWorkers: config.maxParallelWorkers, processInForeground: config.processInForeground };
  789. Object.keys(config.profiles).forEach(profileName => newConfig[PROFILE_NAME_PREFIX + profileName] = config.profiles[profileName]);
  790. await configStorage.set(newConfig);
  791. await upgrade();
  792. }
  793. /*
  794. * Copyright 2010-2020 Gildas Lormeau
  795. * contact : gildas.lormeau <at> gmail.com
  796. *
  797. * This file is part of SingleFile.
  798. *
  799. * The code in this file is free software: you can redistribute it and/or
  800. * modify it under the terms of the GNU Affero General Public License
  801. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  802. * of the License, or (at your option) any later version.
  803. *
  804. * The code in this file is distributed in the hope that it will be useful,
  805. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  806. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  807. * General Public License for more details.
  808. *
  809. * As additional permission under GNU AGPL version 3 section 7, you may
  810. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  811. * AGPL normally required by section 4, provided you include this license
  812. * notice and a URL through which recipients can access the Corresponding
  813. * Source.
  814. */
  815. async function autoSaveIsEnabled(tab) {
  816. if (tab) {
  817. const [allTabsData, rule] = await Promise.all([getPersistent(), getRule(tab.url)]);
  818. return Boolean(allTabsData.autoSaveAll ||
  819. (allTabsData.autoSaveUnpinned && !tab.pinned) ||
  820. (allTabsData[tab.id] && allTabsData[tab.id].autoSave)) &&
  821. (!rule || rule.autoSaveProfile != DISABLED_PROFILE_NAME);
  822. }
  823. }
  824. async function refreshAutoSaveTabs() {
  825. const tabs = (await browser.tabs.query({}));
  826. return Promise.all(tabs.map(async tab => {
  827. const [options, autoSaveEnabled] = await Promise.all([getOptions(tab.url, true), autoSaveIsEnabled(tab)]);
  828. try {
  829. await browser.tabs.sendMessage(tab.id, { method: "content.init", autoSaveEnabled, options });
  830. } catch (error) {
  831. // ignored
  832. }
  833. }));
  834. }
  835. /*
  836. * Copyright 2010-2020 Gildas Lormeau
  837. * contact : gildas.lormeau <at> gmail.com
  838. *
  839. * This file is part of SingleFile.
  840. *
  841. * The code in this file is free software: you can redistribute it and/or
  842. * modify it under the terms of the GNU Affero General Public License
  843. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  844. * of the License, or (at your option) any later version.
  845. *
  846. * The code in this file is distributed in the hope that it will be useful,
  847. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  848. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  849. * General Public License for more details.
  850. *
  851. * As additional permission under GNU AGPL version 3 section 7, you may
  852. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  853. * AGPL normally required by section 4, provided you include this license
  854. * notice and a URL through which recipients can access the Corresponding
  855. * Source.
  856. */
  857. async function onMessage$c(message, sender) {
  858. if (message.method.endsWith(".init")) {
  859. const [optionsAutoSave, options, autoSaveEnabled] = await Promise.all([getOptions(sender.tab.url, true), getOptions(sender.tab.url), autoSaveIsEnabled(sender.tab)]);
  860. return { optionsAutoSave, options, autoSaveEnabled, tabId: sender.tab.id, tabIndex: sender.tab.index };
  861. }
  862. }
  863. /*
  864. * Copyright 2010-2020 Gildas Lormeau
  865. * contact : gildas.lormeau <at> gmail.com
  866. *
  867. * This file is part of SingleFile.
  868. *
  869. * The code in this file is free software: you can redistribute it and/or
  870. * modify it under the terms of the GNU Affero General Public License
  871. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  872. * of the License, or (at your option) any later version.
  873. *
  874. * The code in this file is distributed in the hope that it will be useful,
  875. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  876. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  877. * General Public License for more details.
  878. *
  879. * As additional permission under GNU AGPL version 3 section 7, you may
  880. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  881. * AGPL normally required by section 4, provided you include this license
  882. * notice and a URL through which recipients can access the Corresponding
  883. * Source.
  884. */
  885. const MAX_CONTENT_SIZE$1 = 32 * (1024 * 1024);
  886. const EDITOR_PAGE_URL = "/src/ui/pages/editor.html";
  887. const tabsData = new Map();
  888. const partialContents$1 = new Map();
  889. const EDITOR_URL = browser.runtime.getURL(EDITOR_PAGE_URL);
  890. async function open({ tabIndex, content, filename, compressContent, selfExtractingArchive, extractDataFromPage, insertTextBody, insertMetaCSP, embeddedImage }) {
  891. const createTabProperties = { active: true, url: EDITOR_PAGE_URL };
  892. if (tabIndex != null) {
  893. createTabProperties.index = tabIndex;
  894. }
  895. const tab = await browser.tabs.create(createTabProperties);
  896. tabsData.set(tab.id, {
  897. content,
  898. filename,
  899. compressContent,
  900. selfExtractingArchive,
  901. extractDataFromPage,
  902. insertTextBody,
  903. insertMetaCSP,
  904. embeddedImage
  905. });
  906. }
  907. function onTabRemoved$2(tabId) {
  908. tabsData.delete(tabId);
  909. }
  910. function isEditor(tab) {
  911. return tab.url == EDITOR_URL;
  912. }
  913. async function onMessage$b(message, sender) {
  914. if (message.method.endsWith(".getTabData")) {
  915. const tab = sender.tab;
  916. const tabData = tabsData.get(tab.id);
  917. if (tabData) {
  918. const options = await getOptions(tabData.url);
  919. const content = JSON.stringify(tabData);
  920. for (let blockIndex = 0; blockIndex * MAX_CONTENT_SIZE$1 < content.length; blockIndex++) {
  921. const message = {
  922. method: "editor.setTabData",
  923. compressContent: tabData.compressContent
  924. };
  925. message.truncated = content.length > MAX_CONTENT_SIZE$1;
  926. if (message.truncated) {
  927. message.finished = (blockIndex + 1) * MAX_CONTENT_SIZE$1 > content.length;
  928. message.content = content.substring(blockIndex * MAX_CONTENT_SIZE$1, (blockIndex + 1) * MAX_CONTENT_SIZE$1);
  929. if (message.finished) {
  930. message.options = options;
  931. }
  932. } else {
  933. message.content = content;
  934. options.embeddedImage = tabData.embeddedImage;
  935. message.options = options;
  936. }
  937. await browser.tabs.sendMessage(tab.id, message);
  938. }
  939. }
  940. return {};
  941. }
  942. if (message.method.endsWith(".open")) {
  943. let contents;
  944. const tab = sender.tab;
  945. if (message.truncated) {
  946. contents = partialContents$1.get(tab.id);
  947. if (!contents) {
  948. contents = [];
  949. partialContents$1.set(tab.id, contents);
  950. }
  951. contents.push(message.content);
  952. if (message.finished) {
  953. partialContents$1.delete(tab.id);
  954. }
  955. } else if (message.content) {
  956. contents = [message.content];
  957. }
  958. if (!message.truncated || message.finished) {
  959. const updateTabProperties = { url: EDITOR_PAGE_URL };
  960. await browser.tabs.update(tab.id, updateTabProperties);
  961. const content = message.compressContent ? contents.flat() : contents.join("");
  962. tabsData.set(tab.id, {
  963. url: tab.url,
  964. content,
  965. filename: message.filename,
  966. compressContent: message.compressContent,
  967. selfExtractingArchive: message.selfExtractingArchive,
  968. extractDataFromPageTags: message.extractDataFromPageTags,
  969. insertTextBody: message.insertTextBody,
  970. insertMetaCSP: message.insertMetaCSP,
  971. embeddedImage: message.embeddedImage
  972. });
  973. }
  974. return {};
  975. }
  976. if (message.method.endsWith(".ping")) {
  977. return {};
  978. }
  979. }
  980. /*
  981. * Copyright 2010-2020 Gildas Lormeau
  982. * contact : gildas.lormeau <at> gmail.com
  983. *
  984. * This file is part of SingleFile.
  985. *
  986. * The code in this file is free software: you can redistribute it and/or
  987. * modify it under the terms of the GNU Affero General Public License
  988. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  989. * of the License, or (at your option) any later version.
  990. *
  991. * The code in this file is distributed in the hope that it will be useful,
  992. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  993. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  994. * General Public License for more details.
  995. *
  996. * As additional permission under GNU AGPL version 3 section 7, you may
  997. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  998. * AGPL normally required by section 4, provided you include this license
  999. * notice and a URL through which recipients can access the Corresponding
  1000. * Source.
  1001. */
  1002. /* global browser, XMLHttpRequest */
  1003. const referrers = new Map();
  1004. const REQUEST_ID_HEADER_NAME = "x-single-file-request-id";
  1005. const MAX_CONTENT_SIZE = 8 * (1024 * 1024);
  1006. browser.runtime.onMessage.addListener((message, sender) => {
  1007. if (message.method && message.method.startsWith("singlefile.fetch")) {
  1008. return new Promise(resolve => {
  1009. onRequest(message, sender)
  1010. .then(resolve)
  1011. .catch(error => resolve({ error: error && error.toString() }));
  1012. });
  1013. }
  1014. });
  1015. async function onRequest(message, sender) {
  1016. if (message.method == "singlefile.fetch") {
  1017. try {
  1018. const response = await fetchResource$1(message.url, { referrer: message.referrer, headers: message.headers });
  1019. return sendResponse(sender.tab.id, message.requestId, response);
  1020. } catch (error) {
  1021. return sendResponse(sender.tab.id, message.requestId, { error: error.message, array: [] });
  1022. }
  1023. } else if (message.method == "singlefile.fetchFrame") {
  1024. return browser.tabs.sendMessage(sender.tab.id, message);
  1025. }
  1026. }
  1027. async function sendResponse(tabId, requestId, response) {
  1028. for (let blockIndex = 0; blockIndex * MAX_CONTENT_SIZE <= response.array.length; blockIndex++) {
  1029. const message = {
  1030. method: "singlefile.fetchResponse",
  1031. requestId,
  1032. headers: response.headers,
  1033. status: response.status,
  1034. error: response.error
  1035. };
  1036. message.truncated = response.array.length > MAX_CONTENT_SIZE;
  1037. if (message.truncated) {
  1038. message.finished = (blockIndex + 1) * MAX_CONTENT_SIZE > response.array.length;
  1039. message.array = response.array.slice(blockIndex * MAX_CONTENT_SIZE, (blockIndex + 1) * MAX_CONTENT_SIZE);
  1040. } else {
  1041. message.array = response.array;
  1042. }
  1043. await browser.tabs.sendMessage(tabId, message);
  1044. }
  1045. return {};
  1046. }
  1047. function fetchResource$1(url, options = {}, includeRequestId) {
  1048. return new Promise((resolve, reject) => {
  1049. const xhrRequest = new XMLHttpRequest();
  1050. xhrRequest.withCredentials = true;
  1051. xhrRequest.responseType = "arraybuffer";
  1052. xhrRequest.onerror = event => reject(new Error(event.detail));
  1053. xhrRequest.onreadystatechange = () => {
  1054. if (xhrRequest.readyState == XMLHttpRequest.DONE) {
  1055. if (xhrRequest.status || xhrRequest.response.byteLength) {
  1056. if ((xhrRequest.status == 401 || xhrRequest.status == 403 || xhrRequest.status == 404) && !includeRequestId) {
  1057. fetchResource$1(url, options, true)
  1058. .then(resolve)
  1059. .catch(reject);
  1060. } else {
  1061. resolve({
  1062. arrayBuffer: xhrRequest.response,
  1063. array: Array.from(new Uint8Array(xhrRequest.response)),
  1064. headers: { "content-type": xhrRequest.getResponseHeader("Content-Type") },
  1065. status: xhrRequest.status
  1066. });
  1067. }
  1068. } else {
  1069. reject(new Error("Empty response"));
  1070. }
  1071. }
  1072. };
  1073. xhrRequest.open("GET", url, true);
  1074. if (options.headers) {
  1075. for (const entry of Object.entries(options.headers)) {
  1076. xhrRequest.setRequestHeader(entry[0], entry[1]);
  1077. }
  1078. }
  1079. if (includeRequestId) {
  1080. const randomId = String(Math.random()).substring(2);
  1081. setReferrer(randomId, options.referrer);
  1082. xhrRequest.setRequestHeader(REQUEST_ID_HEADER_NAME, randomId);
  1083. }
  1084. xhrRequest.send();
  1085. });
  1086. }
  1087. function setReferrer(requestId, referrer) {
  1088. referrers.set(requestId, referrer);
  1089. }
  1090. /*
  1091. * Copyright 2010-2020 Gildas Lormeau
  1092. * contact : gildas.lormeau <at> gmail.com
  1093. *
  1094. * This file is part of SingleFile.
  1095. *
  1096. * The code in this file is free software: you can redistribute it and/or
  1097. * modify it under the terms of the GNU Affero General Public License
  1098. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1099. * of the License, or (at your option) any later version.
  1100. *
  1101. * The code in this file is distributed in the hope that it will be useful,
  1102. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1103. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1104. * General Public License for more details.
  1105. *
  1106. * As additional permission under GNU AGPL version 3 section 7, you may
  1107. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1108. * AGPL normally required by section 4, provided you include this license
  1109. * notice and a URL through which recipients can access the Corresponding
  1110. * Source.
  1111. */
  1112. let referrerOnErrorEnabled = false;
  1113. function onMessage$a(message) {
  1114. if (message.method.endsWith(".enableReferrerOnError")) {
  1115. enableReferrerOnError();
  1116. return {};
  1117. }
  1118. if (message.method.endsWith(".disableReferrerOnError")) {
  1119. disableReferrerOnError();
  1120. return {};
  1121. }
  1122. }
  1123. function injectRefererHeader(details) {
  1124. if (referrerOnErrorEnabled) {
  1125. let requestIdHeader = details.requestHeaders.find(header => header.name === REQUEST_ID_HEADER_NAME);
  1126. if (requestIdHeader) {
  1127. details.requestHeaders = details.requestHeaders.filter(header => header.name !== REQUEST_ID_HEADER_NAME);
  1128. const referrer = referrers.get(requestIdHeader.value);
  1129. if (referrer) {
  1130. referrers.delete(requestIdHeader.value);
  1131. const header = details.requestHeaders.find(header => header.name.toLowerCase() === "referer");
  1132. if (!header) {
  1133. details.requestHeaders.push({ name: "Referer", value: referrer });
  1134. return { requestHeaders: details.requestHeaders };
  1135. }
  1136. }
  1137. }
  1138. }
  1139. }
  1140. function enableReferrerOnError() {
  1141. if (!referrerOnErrorEnabled) {
  1142. try {
  1143. browser.webRequest.onBeforeSendHeaders.addListener(injectRefererHeader, { urls: ["<all_urls>"] }, ["blocking", "requestHeaders", "extraHeaders"]);
  1144. } catch (error) {
  1145. browser.webRequest.onBeforeSendHeaders.addListener(injectRefererHeader, { urls: ["<all_urls>"] }, ["blocking", "requestHeaders"]);
  1146. }
  1147. referrerOnErrorEnabled = true;
  1148. }
  1149. }
  1150. function disableReferrerOnError() {
  1151. try {
  1152. browser.webRequest.onBeforeSendHeaders.removeListener(injectRefererHeader);
  1153. } catch (error) {
  1154. // ignored
  1155. }
  1156. referrerOnErrorEnabled = false;
  1157. }
  1158. /*
  1159. * Copyright 2010-2020 Gildas Lormeau
  1160. * contact : gildas.lormeau <at> gmail.com
  1161. *
  1162. * This file is part of SingleFile.
  1163. *
  1164. * The code in this file is free software: you can redistribute it and/or
  1165. * modify it under the terms of the GNU Affero General Public License
  1166. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1167. * of the License, or (at your option) any later version.
  1168. *
  1169. * The code in this file is distributed in the hope that it will be useful,
  1170. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1171. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1172. * General Public License for more details.
  1173. *
  1174. * As additional permission under GNU AGPL version 3 section 7, you may
  1175. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1176. * AGPL normally required by section 4, provided you include this license
  1177. * notice and a URL through which recipients can access the Corresponding
  1178. * Source.
  1179. */
  1180. async function queryTabs$1(options) {
  1181. const tabs = await browser.tabs.query(options);
  1182. return tabs.sort((tab1, tab2) => tab1.index - tab2.index);
  1183. }
  1184. function extractAuthCode(authURL) {
  1185. return new Promise((resolve, reject) => {
  1186. browser.tabs.onUpdated.addListener(onTabUpdated);
  1187. function onTabUpdated(tabId, changeInfo) {
  1188. if (changeInfo && changeInfo.url && changeInfo.url.startsWith(authURL)) {
  1189. browser.tabs.onUpdated.removeListener(onTabUpdated);
  1190. const code = new URLSearchParams(new URL(changeInfo.url).search).get("code");
  1191. if (code) {
  1192. browser.tabs.remove(tabId);
  1193. resolve(code);
  1194. } else {
  1195. reject();
  1196. }
  1197. }
  1198. }
  1199. });
  1200. }
  1201. async function launchWebAuthFlow(options) {
  1202. const tab = await browser.tabs.create({ url: options.url, active: true });
  1203. return new Promise((resolve, reject) => {
  1204. browser.tabs.onRemoved.addListener(onTabRemoved);
  1205. function onTabRemoved(tabId) {
  1206. if (tabId == tab.id) {
  1207. browser.tabs.onRemoved.removeListener(onTabRemoved);
  1208. reject(new Error("code_required"));
  1209. }
  1210. }
  1211. });
  1212. }
  1213. /*
  1214. * Copyright 2010-2020 Gildas Lormeau
  1215. * contact : gildas.lormeau <at> gmail.com
  1216. *
  1217. * This file is part of SingleFile.
  1218. *
  1219. * The code in this file is free software: you can redistribute it and/or
  1220. * modify it under the terms of the GNU Affero General Public License
  1221. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1222. * of the License, or (at your option) any later version.
  1223. *
  1224. * The code in this file is distributed in the hope that it will be useful,
  1225. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1226. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1227. * General Public License for more details.
  1228. *
  1229. * As additional permission under GNU AGPL version 3 section 7, you may
  1230. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1231. * AGPL normally required by section 4, provided you include this license
  1232. * notice and a URL through which recipients can access the Corresponding
  1233. * Source.
  1234. */
  1235. const DEFAULT_ICON_PATH = "/src/ui/resources/icon_128.png";
  1236. const WAIT_ICON_PATH_PREFIX = "/src/ui/resources/icon_128_wait";
  1237. const BUTTON_DEFAULT_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonDefaultTooltip");
  1238. const BUTTON_BLOCKED_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonBlockedTooltip");
  1239. const BUTTON_DEFAULT_BADGE_MESSAGE = "";
  1240. const BUTTON_INITIALIZING_BADGE_MESSAGE = browser.i18n.getMessage("buttonInitializingBadge");
  1241. const BUTTON_INITIALIZING_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonInitializingTooltip");
  1242. const BUTTON_ERROR_BADGE_MESSAGE = browser.i18n.getMessage("buttonErrorBadge");
  1243. const BUTTON_BLOCKED_BADGE_MESSAGE = browser.i18n.getMessage("buttonBlockedBadge");
  1244. const BUTTON_OK_BADGE_MESSAGE = browser.i18n.getMessage("buttonOKBadge");
  1245. const BUTTON_SAVE_PROGRESS_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonSaveProgressTooltip");
  1246. const BUTTON_UPLOAD_PROGRESS_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonUploadProgressTooltip");
  1247. const BUTTON_AUTOSAVE_ACTIVE_BADGE_MESSAGE = browser.i18n.getMessage("buttonAutoSaveActiveBadge");
  1248. const BUTTON_AUTOSAVE_ACTIVE_TOOLTIP_MESSAGE = browser.i18n.getMessage("buttonAutoSaveActiveTooltip");
  1249. const DEFAULT_COLOR = [2, 147, 20, 192];
  1250. const ACTIVE_COLOR = [4, 229, 36, 192];
  1251. const FORBIDDEN_COLOR = [255, 255, 255, 1];
  1252. const ERROR_COLOR = [229, 4, 12, 192];
  1253. const AUTOSAVE_DEFAULT_COLOR = [208, 208, 208, 192];
  1254. const AUTOSAVE_INITIALIZING_COLOR = [64, 64, 64, 192];
  1255. const INJECT_SCRIPTS_STEP$1 = 1;
  1256. const BUTTON_STATES = {
  1257. default: {
  1258. setBadgeBackgroundColor: { color: DEFAULT_COLOR },
  1259. setBadgeText: { text: BUTTON_DEFAULT_BADGE_MESSAGE },
  1260. setTitle: { title: BUTTON_DEFAULT_TOOLTIP_MESSAGE },
  1261. setIcon: { path: DEFAULT_ICON_PATH }
  1262. },
  1263. inject: {
  1264. setBadgeBackgroundColor: { color: DEFAULT_COLOR },
  1265. setBadgeText: { text: BUTTON_INITIALIZING_BADGE_MESSAGE },
  1266. setTitle: { title: BUTTON_INITIALIZING_TOOLTIP_MESSAGE },
  1267. },
  1268. execute: {
  1269. setBadgeBackgroundColor: { color: ACTIVE_COLOR },
  1270. setBadgeText: { text: BUTTON_INITIALIZING_BADGE_MESSAGE },
  1271. },
  1272. progress: {
  1273. setBadgeBackgroundColor: { color: ACTIVE_COLOR },
  1274. setBadgeText: { text: BUTTON_DEFAULT_BADGE_MESSAGE }
  1275. },
  1276. edit: {
  1277. setBadgeBackgroundColor: { color: DEFAULT_COLOR },
  1278. setBadgeText: { text: BUTTON_DEFAULT_BADGE_MESSAGE },
  1279. setTitle: { title: BUTTON_DEFAULT_TOOLTIP_MESSAGE },
  1280. setIcon: { path: DEFAULT_ICON_PATH }
  1281. },
  1282. end: {
  1283. setBadgeBackgroundColor: { color: ACTIVE_COLOR },
  1284. setBadgeText: { text: BUTTON_OK_BADGE_MESSAGE },
  1285. setTitle: { title: BUTTON_DEFAULT_TOOLTIP_MESSAGE },
  1286. setIcon: { path: DEFAULT_ICON_PATH }
  1287. },
  1288. error: {
  1289. setBadgeBackgroundColor: { color: ERROR_COLOR },
  1290. setBadgeText: { text: BUTTON_ERROR_BADGE_MESSAGE },
  1291. setTitle: { title: BUTTON_DEFAULT_BADGE_MESSAGE },
  1292. setIcon: { path: DEFAULT_ICON_PATH }
  1293. },
  1294. forbidden: {
  1295. setBadgeBackgroundColor: { color: FORBIDDEN_COLOR },
  1296. setBadgeText: { text: BUTTON_BLOCKED_BADGE_MESSAGE },
  1297. setTitle: { title: BUTTON_BLOCKED_TOOLTIP_MESSAGE },
  1298. setIcon: { path: DEFAULT_ICON_PATH }
  1299. },
  1300. autosave: {
  1301. inject: {
  1302. setBadgeBackgroundColor: { color: AUTOSAVE_INITIALIZING_COLOR },
  1303. setBadgeText: { text: BUTTON_AUTOSAVE_ACTIVE_BADGE_MESSAGE },
  1304. setTitle: { title: BUTTON_AUTOSAVE_ACTIVE_TOOLTIP_MESSAGE },
  1305. setIcon: { path: DEFAULT_ICON_PATH }
  1306. },
  1307. default: {
  1308. setBadgeBackgroundColor: { color: AUTOSAVE_DEFAULT_COLOR },
  1309. setBadgeText: { text: BUTTON_AUTOSAVE_ACTIVE_BADGE_MESSAGE },
  1310. setTitle: { title: BUTTON_AUTOSAVE_ACTIVE_TOOLTIP_MESSAGE },
  1311. setIcon: { path: DEFAULT_ICON_PATH }
  1312. }
  1313. }
  1314. };
  1315. let business$2;
  1316. browser.browserAction.onClicked.addListener(async tab => {
  1317. const highlightedTabs = await queryTabs$1({ currentWindow: true, highlighted: true });
  1318. if (highlightedTabs.length <= 1) {
  1319. toggleSaveTab(tab);
  1320. } else {
  1321. business$2.saveTabs(highlightedTabs);
  1322. }
  1323. function toggleSaveTab(tab) {
  1324. if (business$2.isSavingTab(tab)) {
  1325. business$2.cancelTab(tab.id);
  1326. } else {
  1327. business$2.saveTabs([tab]);
  1328. }
  1329. }
  1330. });
  1331. function init$3(businessApi) {
  1332. business$2 = businessApi;
  1333. }
  1334. function onMessage$9(message, sender) {
  1335. if (message.method.endsWith(".processInit")) {
  1336. const allTabsData = getTemporary(sender.tab.id);
  1337. delete allTabsData[sender.tab.id].button;
  1338. refreshTab$2(sender.tab);
  1339. }
  1340. if (message.method.endsWith(".processProgress")) {
  1341. if (message.maxIndex) {
  1342. onSaveProgress(sender.tab.id, message.index, message.maxIndex);
  1343. }
  1344. }
  1345. if (message.method.endsWith(".processEnd")) {
  1346. onEnd$1(sender.tab.id);
  1347. }
  1348. if (message.method.endsWith(".processError")) {
  1349. if (message.error) {
  1350. console.error("Initialization error", message.error); // eslint-disable-line no-console
  1351. }
  1352. onError$1(sender.tab.id);
  1353. }
  1354. if (message.method.endsWith(".processCancelled")) {
  1355. onCancelled$1(sender.tab);
  1356. }
  1357. return Promise.resolve({});
  1358. }
  1359. function onStart$1(tabId, step, autoSave) {
  1360. let state;
  1361. if (autoSave) {
  1362. state = getButtonState("inject", true);
  1363. } else {
  1364. state = step == INJECT_SCRIPTS_STEP$1 ? getButtonState("inject") : getButtonState("execute");
  1365. state.setTitle = { title: BUTTON_INITIALIZING_TOOLTIP_MESSAGE + " (" + step + "/2)" };
  1366. state.setIcon = { path: WAIT_ICON_PATH_PREFIX + "0.png" };
  1367. }
  1368. refresh(tabId, state);
  1369. }
  1370. function onError$1(tabId) {
  1371. refresh(tabId, getButtonState("error"));
  1372. }
  1373. function onEdit$1(tabId) {
  1374. refresh(tabId, getButtonState("edit"));
  1375. }
  1376. function onEnd$1(tabId, autoSave) {
  1377. refresh(tabId, autoSave ? getButtonState("default", true) : getButtonState("end"));
  1378. }
  1379. function onForbiddenDomain$1(tab) {
  1380. refresh(tab.id, getButtonState("forbidden"));
  1381. }
  1382. function onCancelled$1(tab) {
  1383. refreshTab$2(tab);
  1384. }
  1385. function onSaveProgress(tabId, index, maxIndex) {
  1386. onProgress(tabId, index, maxIndex, BUTTON_SAVE_PROGRESS_TOOLTIP_MESSAGE);
  1387. }
  1388. function onUploadProgress$1(tabId, index, maxIndex) {
  1389. onProgress(tabId, index, maxIndex, BUTTON_UPLOAD_PROGRESS_TOOLTIP_MESSAGE);
  1390. }
  1391. function onProgress(tabId, index, maxIndex, tooltipMessage) {
  1392. const progress = Math.max(Math.min(20, Math.floor((index / maxIndex) * 20)), 0);
  1393. const barProgress = Math.min(Math.floor((index / maxIndex) * 8), 8);
  1394. const path = WAIT_ICON_PATH_PREFIX + barProgress + ".png";
  1395. const state = getButtonState("progress");
  1396. state.setTitle = { title: tooltipMessage + (progress * 5) + "%" };
  1397. state.setIcon = { path };
  1398. refresh(tabId, state);
  1399. }
  1400. async function refreshTab$2(tab) {
  1401. const autoSave = await autoSaveIsEnabled(tab);
  1402. const state = getButtonState("default", autoSave);
  1403. await refresh(tab.id, state);
  1404. }
  1405. async function refresh(tabId, state) {
  1406. try {
  1407. const allTabsData = getTemporary(tabId);
  1408. if (state) {
  1409. if (!allTabsData[tabId].button) {
  1410. allTabsData[tabId].button = { lastState: null };
  1411. }
  1412. const lastState = allTabsData[tabId].button.lastState || {};
  1413. const newState = {};
  1414. Object.keys(state).forEach(property => {
  1415. if (state[property] !== undefined && (JSON.stringify(lastState[property]) != JSON.stringify(state[property]))) {
  1416. newState[property] = state[property];
  1417. }
  1418. });
  1419. if (Object.keys(newState).length) {
  1420. allTabsData[tabId].button.lastState = state;
  1421. await refreshAsync(tabId, newState);
  1422. }
  1423. }
  1424. } catch (error) {
  1425. // ignored
  1426. }
  1427. }
  1428. async function refreshAsync(tabId, state) {
  1429. for (const browserActionMethod of Object.keys(state)) {
  1430. await refreshProperty(tabId, browserActionMethod, state[browserActionMethod]);
  1431. }
  1432. }
  1433. async function refreshProperty(tabId, browserActionMethod, browserActionParameter) {
  1434. const actionMethodSupported = browserActionMethod != "setBadgeBackgroundColor" || BADGE_COLOR_SUPPORTED;
  1435. if (browser.browserAction[browserActionMethod] && actionMethodSupported) {
  1436. const parameter = JSON.parse(JSON.stringify(browserActionParameter));
  1437. parameter.tabId = tabId;
  1438. await browser.browserAction[browserActionMethod](parameter);
  1439. }
  1440. }
  1441. function getButtonState(name, autoSave) {
  1442. return JSON.parse(JSON.stringify(autoSave ? BUTTON_STATES.autosave[name] : BUTTON_STATES[name]));
  1443. }
  1444. /*
  1445. * Copyright 2010-2020 Gildas Lormeau
  1446. * contact : gildas.lormeau <at> gmail.com
  1447. *
  1448. * This file is part of SingleFile.
  1449. *
  1450. * The code in this file is free software: you can redistribute it and/or
  1451. * modify it under the terms of the GNU Affero General Public License
  1452. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  1453. * of the License, or (at your option) any later version.
  1454. *
  1455. * The code in this file is distributed in the hope that it will be useful,
  1456. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  1457. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  1458. * General Public License for more details.
  1459. *
  1460. * As additional permission under GNU AGPL version 3 section 7, you may
  1461. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  1462. * AGPL normally required by section 4, provided you include this license
  1463. * notice and a URL through which recipients can access the Corresponding
  1464. * Source.
  1465. */
  1466. const menus = browser.menus;
  1467. const BROWSER_MENUS_API_SUPPORTED = menus && menus.onClicked && menus.create && menus.update && menus.removeAll;
  1468. const MENU_ID_SAVE_PAGE = "save-page";
  1469. const MENU_ID_EDIT_AND_SAVE_PAGE = "edit-and-save-page";
  1470. const MENU_ID_SAVE_WITH_PROFILE = "save-with-profile";
  1471. const MENU_ID_SAVE_SELECTED_LINKS = "save-selected-links";
  1472. const MENU_ID_VIEW_PENDINGS = "view-pendings";
  1473. const MENU_ID_SELECT_PROFILE = "select-profile";
  1474. const MENU_ID_SAVE_WITH_PROFILE_PREFIX = "wasve-with-profile-";
  1475. const MENU_ID_SELECT_PROFILE_PREFIX = "select-profile-";
  1476. const MENU_ID_ASSOCIATE_WITH_PROFILE = "associate-with-profile";
  1477. const MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX = "associate-with-profile-";
  1478. const MENU_ID_SAVE_SELECTED = "save-selected";
  1479. const MENU_ID_SAVE_FRAME = "save-frame";
  1480. const MENU_ID_SAVE_TABS = "save-tabs";
  1481. const MENU_ID_SAVE_SELECTED_TABS = "save-selected-tabs";
  1482. const MENU_ID_SAVE_UNPINNED_TABS = "save-unpinned-tabs";
  1483. const MENU_ID_SAVE_ALL_TABS = "save-all-tabs";
  1484. const MENU_ID_BATCH_SAVE_URLS = "batch-save-urls";
  1485. const MENU_ID_BUTTON_SAVE_SELECTED_TABS = "button-" + MENU_ID_SAVE_SELECTED_TABS;
  1486. const MENU_ID_BUTTON_SAVE_UNPINNED_TABS = "button-" + MENU_ID_SAVE_UNPINNED_TABS;
  1487. const MENU_ID_BUTTON_SAVE_ALL_TABS = "button-" + MENU_ID_SAVE_ALL_TABS;
  1488. const MENU_ID_AUTO_SAVE = "auto-save";
  1489. const MENU_ID_AUTO_SAVE_DISABLED = "auto-save-disabled";
  1490. const MENU_ID_AUTO_SAVE_TAB = "auto-save-tab";
  1491. const MENU_ID_AUTO_SAVE_UNPINNED = "auto-save-unpinned";
  1492. const MENU_ID_AUTO_SAVE_ALL = "auto-save-all";
  1493. const MENU_CREATE_DOMAIN_RULE_MESSAGE = browser.i18n.getMessage("menuCreateDomainRule");
  1494. const MENU_UPDATE_RULE_MESSAGE = browser.i18n.getMessage("menuUpdateRule");
  1495. const MENU_SAVE_PAGE_MESSAGE = browser.i18n.getMessage("menuSavePage");
  1496. const MENU_SAVE_WITH_PROFILE = browser.i18n.getMessage("menuSaveWithProfile");
  1497. const MENU_SAVE_SELECTED_LINKS = browser.i18n.getMessage("menuSaveSelectedLinks");
  1498. const MENU_EDIT_PAGE_MESSAGE = browser.i18n.getMessage("menuEditPage");
  1499. const MENU_EDIT_AND_SAVE_PAGE_MESSAGE = browser.i18n.getMessage("menuEditAndSavePage");
  1500. const MENU_VIEW_PENDINGS_MESSAGE = browser.i18n.getMessage("menuViewPendingSaves");
  1501. const MENU_SAVE_SELECTION_MESSAGE = browser.i18n.getMessage("menuSaveSelection");
  1502. const MENU_SAVE_FRAME_MESSAGE = browser.i18n.getMessage("menuSaveFrame");
  1503. const MENU_SAVE_TABS_MESSAGE = browser.i18n.getMessage("menuSaveTabs");
  1504. const MENU_SAVE_SELECTED_TABS_MESSAGE = browser.i18n.getMessage("menuSaveSelectedTabs");
  1505. const MENU_SAVE_UNPINNED_TABS_MESSAGE = browser.i18n.getMessage("menuSaveUnpinnedTabs");
  1506. const MENU_SAVE_ALL_TABS_MESSAGE = browser.i18n.getMessage("menuSaveAllTabs");
  1507. const MENU_BATCH_SAVE_URLS_MESSAGE = browser.i18n.getMessage("menuBatchSaveUrls");
  1508. const MENU_SELECT_PROFILE_MESSAGE = browser.i18n.getMessage("menuSelectProfile");
  1509. const PROFILE_DEFAULT_SETTINGS_MESSAGE = browser.i18n.getMessage("profileDefaultSettings");
  1510. const MENU_AUTOSAVE_MESSAGE = browser.i18n.getMessage("menuAutoSave");
  1511. const MENU_AUTOSAVE_DISABLED_MESSAGE = browser.i18n.getMessage("menuAutoSaveDisabled");
  1512. const MENU_AUTOSAVE_TAB_MESSAGE = browser.i18n.getMessage("menuAutoSaveTab");
  1513. const MENU_AUTOSAVE_UNPINNED_TABS_MESSAGE = browser.i18n.getMessage("menuAutoSaveUnpinnedTabs");
  1514. const MENU_AUTOSAVE_ALL_TABS_MESSAGE = browser.i18n.getMessage("menuAutoSaveAllTabs");
  1515. const MENU_TOP_VISIBLE_ENTRIES = [
  1516. MENU_ID_EDIT_AND_SAVE_PAGE,
  1517. MENU_ID_SAVE_SELECTED_LINKS,
  1518. MENU_ID_SAVE_SELECTED,
  1519. MENU_ID_SAVE_FRAME,
  1520. MENU_ID_AUTO_SAVE,
  1521. MENU_ID_ASSOCIATE_WITH_PROFILE
  1522. ];
  1523. const menusCheckedState = new Map();
  1524. const menusTitleState = new Map();
  1525. let contextMenuVisibleState = true;
  1526. let allMenuVisibleState = true;
  1527. let profileIndexes = new Map();
  1528. let menusCreated, pendingRefresh, business$1;
  1529. Promise.resolve().then(initialize);
  1530. function init$2(businessApi) {
  1531. business$1 = businessApi;
  1532. }
  1533. function onMessage$8(message) {
  1534. if (message.method.endsWith("refreshMenu")) {
  1535. createMenus();
  1536. return Promise.resolve({});
  1537. }
  1538. }
  1539. async function createMenus(tab) {
  1540. const [profiles, allTabsData] = await Promise.all([getProfiles(), getPersistent()]);
  1541. const options = await getOptions(tab && tab.url);
  1542. if (BROWSER_MENUS_API_SUPPORTED && options) {
  1543. const pageContextsEnabled = ["page", "frame", "image", "link", "video", "audio", "selection"];
  1544. const defaultContextsDisabled = [];
  1545. if (options.browserActionMenuEnabled) {
  1546. defaultContextsDisabled.push("browser_action");
  1547. }
  1548. if (options.tabMenuEnabled) {
  1549. try {
  1550. await menus.create({
  1551. id: "temporary-id",
  1552. contexts: ["tab"],
  1553. title: "title"
  1554. });
  1555. defaultContextsDisabled.push("tab");
  1556. } catch (error) {
  1557. options.tabMenuEnabled = false;
  1558. }
  1559. }
  1560. await menus.removeAll();
  1561. const defaultContextsEnabled = defaultContextsDisabled.concat(...pageContextsEnabled);
  1562. const defaultContexts = options.contextMenuEnabled ? defaultContextsEnabled : defaultContextsDisabled;
  1563. menus.create({
  1564. id: MENU_ID_SAVE_PAGE,
  1565. contexts: defaultContexts,
  1566. title: MENU_SAVE_PAGE_MESSAGE
  1567. });
  1568. menus.create({
  1569. id: MENU_ID_EDIT_AND_SAVE_PAGE,
  1570. contexts: defaultContexts,
  1571. title: MENU_EDIT_AND_SAVE_PAGE_MESSAGE
  1572. });
  1573. menus.create({
  1574. id: MENU_ID_SAVE_SELECTED_LINKS,
  1575. contexts: options.contextMenuEnabled ? defaultContextsDisabled.concat(["selection"]) : defaultContextsDisabled,
  1576. title: MENU_SAVE_SELECTED_LINKS
  1577. });
  1578. if (Object.keys(profiles).length > 1) {
  1579. menus.create({
  1580. id: MENU_ID_SAVE_WITH_PROFILE,
  1581. contexts: defaultContexts,
  1582. title: MENU_SAVE_WITH_PROFILE
  1583. });
  1584. }
  1585. if (options.contextMenuEnabled) {
  1586. menus.create({
  1587. id: "separator-1",
  1588. contexts: pageContextsEnabled,
  1589. type: "separator"
  1590. });
  1591. }
  1592. menus.create({
  1593. id: MENU_ID_SAVE_SELECTED,
  1594. contexts: defaultContexts,
  1595. title: MENU_SAVE_SELECTION_MESSAGE
  1596. });
  1597. if (options.contextMenuEnabled) {
  1598. menus.create({
  1599. id: MENU_ID_SAVE_FRAME,
  1600. contexts: ["frame"],
  1601. title: MENU_SAVE_FRAME_MESSAGE
  1602. });
  1603. }
  1604. menus.create({
  1605. id: MENU_ID_SAVE_TABS,
  1606. contexts: defaultContextsDisabled,
  1607. title: MENU_SAVE_TABS_MESSAGE
  1608. });
  1609. menus.create({
  1610. id: MENU_ID_BUTTON_SAVE_SELECTED_TABS,
  1611. contexts: defaultContextsDisabled,
  1612. title: MENU_SAVE_SELECTED_TABS_MESSAGE,
  1613. parentId: MENU_ID_SAVE_TABS
  1614. });
  1615. menus.create({
  1616. id: MENU_ID_BUTTON_SAVE_UNPINNED_TABS,
  1617. contexts: defaultContextsDisabled,
  1618. title: MENU_SAVE_UNPINNED_TABS_MESSAGE,
  1619. parentId: MENU_ID_SAVE_TABS
  1620. });
  1621. menus.create({
  1622. id: MENU_ID_BUTTON_SAVE_ALL_TABS,
  1623. contexts: defaultContextsDisabled,
  1624. title: MENU_SAVE_ALL_TABS_MESSAGE,
  1625. parentId: MENU_ID_SAVE_TABS
  1626. });
  1627. if (options.contextMenuEnabled) {
  1628. if (SELECTABLE_TABS_SUPPORTED) {
  1629. menus.create({
  1630. id: MENU_ID_SAVE_SELECTED_TABS,
  1631. contexts: pageContextsEnabled,
  1632. title: MENU_SAVE_SELECTED_TABS_MESSAGE
  1633. });
  1634. }
  1635. menus.create({
  1636. id: MENU_ID_SAVE_UNPINNED_TABS,
  1637. contexts: pageContextsEnabled,
  1638. title: MENU_SAVE_UNPINNED_TABS_MESSAGE
  1639. });
  1640. menus.create({
  1641. id: MENU_ID_SAVE_ALL_TABS,
  1642. contexts: pageContextsEnabled,
  1643. title: MENU_SAVE_ALL_TABS_MESSAGE
  1644. });
  1645. menus.create({
  1646. id: "separator-2",
  1647. contexts: pageContextsEnabled,
  1648. type: "separator"
  1649. });
  1650. }
  1651. if (Object.keys(profiles).length > 1) {
  1652. menus.create({
  1653. id: MENU_ID_SELECT_PROFILE,
  1654. title: MENU_SELECT_PROFILE_MESSAGE,
  1655. contexts: defaultContexts,
  1656. });
  1657. menus.create({
  1658. id: MENU_ID_SAVE_WITH_PROFILE_PREFIX + "default",
  1659. contexts: defaultContexts,
  1660. title: PROFILE_DEFAULT_SETTINGS_MESSAGE,
  1661. parentId: MENU_ID_SAVE_WITH_PROFILE
  1662. });
  1663. const defaultProfileId = MENU_ID_SELECT_PROFILE_PREFIX + "default";
  1664. const defaultProfileChecked = !allTabsData.profileName || allTabsData.profileName == DEFAULT_PROFILE_NAME;
  1665. menus.create({
  1666. id: defaultProfileId,
  1667. type: "radio",
  1668. contexts: defaultContexts,
  1669. title: PROFILE_DEFAULT_SETTINGS_MESSAGE,
  1670. checked: defaultProfileChecked,
  1671. parentId: MENU_ID_SELECT_PROFILE
  1672. });
  1673. menusCheckedState.set(defaultProfileId, defaultProfileChecked);
  1674. menus.create({
  1675. id: MENU_ID_ASSOCIATE_WITH_PROFILE,
  1676. title: MENU_CREATE_DOMAIN_RULE_MESSAGE,
  1677. contexts: defaultContexts,
  1678. });
  1679. menusTitleState.set(MENU_ID_ASSOCIATE_WITH_PROFILE, MENU_CREATE_DOMAIN_RULE_MESSAGE);
  1680. let rule;
  1681. if (tab && tab.url) {
  1682. rule = await getRule(tab.url, true);
  1683. }
  1684. const currentProfileId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "current";
  1685. const currentProfileChecked = !rule || (rule.profile == CURRENT_PROFILE_NAME);
  1686. menus.create({
  1687. id: currentProfileId,
  1688. type: "radio",
  1689. contexts: defaultContexts,
  1690. title: CURRENT_PROFILE_NAME,
  1691. checked: currentProfileChecked,
  1692. parentId: MENU_ID_ASSOCIATE_WITH_PROFILE
  1693. });
  1694. menusCheckedState.set(currentProfileId, currentProfileChecked);
  1695. const associatedDefaultProfileId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default";
  1696. const associatedDefaultProfileChecked = Boolean(rule) && (rule.profile == DEFAULT_PROFILE_NAME);
  1697. menus.create({
  1698. id: associatedDefaultProfileId,
  1699. type: "radio",
  1700. contexts: defaultContexts,
  1701. title: PROFILE_DEFAULT_SETTINGS_MESSAGE,
  1702. checked: associatedDefaultProfileChecked,
  1703. parentId: MENU_ID_ASSOCIATE_WITH_PROFILE
  1704. });
  1705. menusCheckedState.set(associatedDefaultProfileId, associatedDefaultProfileChecked);
  1706. profileIndexes = new Map();
  1707. Object.keys(profiles).forEach((profileName, profileIndex) => {
  1708. if (profileName != DEFAULT_PROFILE_NAME) {
  1709. let profileId = MENU_ID_SAVE_WITH_PROFILE_PREFIX + profileIndex;
  1710. menus.create({
  1711. id: profileId,
  1712. contexts: defaultContexts,
  1713. title: profileName,
  1714. parentId: MENU_ID_SAVE_WITH_PROFILE
  1715. });
  1716. profileId = MENU_ID_SELECT_PROFILE_PREFIX + profileIndex;
  1717. let profileChecked = allTabsData.profileName == profileName;
  1718. menus.create({
  1719. id: profileId,
  1720. type: "radio",
  1721. contexts: defaultContexts,
  1722. title: profileName,
  1723. checked: profileChecked,
  1724. parentId: MENU_ID_SELECT_PROFILE
  1725. });
  1726. menusCheckedState.set(profileId, profileChecked);
  1727. profileId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + profileIndex;
  1728. profileChecked = Boolean(rule) && rule.profile == profileName;
  1729. menus.create({
  1730. id: profileId,
  1731. type: "radio",
  1732. contexts: defaultContexts,
  1733. title: profileName,
  1734. checked: profileChecked,
  1735. parentId: MENU_ID_ASSOCIATE_WITH_PROFILE
  1736. });
  1737. menusCheckedState.set(profileId, profileChecked);
  1738. profileIndexes.set(profileName, profileIndex);
  1739. }
  1740. });
  1741. if (options.contextMenuEnabled) {
  1742. menus.create({
  1743. id: "separator-3",
  1744. contexts: pageContextsEnabled,
  1745. type: "separator"
  1746. });
  1747. }
  1748. }
  1749. if (AUTO_SAVE_SUPPORTED) {
  1750. menus.create({
  1751. id: MENU_ID_AUTO_SAVE,
  1752. contexts: defaultContexts,
  1753. title: MENU_AUTOSAVE_MESSAGE
  1754. });
  1755. menus.create({
  1756. id: MENU_ID_AUTO_SAVE_DISABLED,
  1757. type: "radio",
  1758. title: MENU_AUTOSAVE_DISABLED_MESSAGE,
  1759. contexts: defaultContexts,
  1760. checked: true,
  1761. parentId: MENU_ID_AUTO_SAVE
  1762. });
  1763. menusCheckedState.set(MENU_ID_AUTO_SAVE_DISABLED, true);
  1764. menus.create({
  1765. id: MENU_ID_AUTO_SAVE_TAB,
  1766. type: "radio",
  1767. title: MENU_AUTOSAVE_TAB_MESSAGE,
  1768. contexts: defaultContexts,
  1769. checked: false,
  1770. parentId: MENU_ID_AUTO_SAVE
  1771. });
  1772. menusCheckedState.set(MENU_ID_AUTO_SAVE_TAB, false);
  1773. menus.create({
  1774. id: MENU_ID_AUTO_SAVE_UNPINNED,
  1775. type: "radio",
  1776. title: MENU_AUTOSAVE_UNPINNED_TABS_MESSAGE,
  1777. contexts: defaultContexts,
  1778. checked: false,
  1779. parentId: MENU_ID_AUTO_SAVE
  1780. });
  1781. menusCheckedState.set(MENU_ID_AUTO_SAVE_UNPINNED, false);
  1782. menus.create({
  1783. id: MENU_ID_AUTO_SAVE_ALL,
  1784. type: "radio",
  1785. title: MENU_AUTOSAVE_ALL_TABS_MESSAGE,
  1786. contexts: defaultContexts,
  1787. checked: false,
  1788. parentId: MENU_ID_AUTO_SAVE
  1789. });
  1790. menusCheckedState.set(MENU_ID_AUTO_SAVE_ALL, false);
  1791. menus.create({
  1792. id: "separator-4",
  1793. contexts: defaultContexts,
  1794. type: "separator"
  1795. });
  1796. }
  1797. menus.create({
  1798. id: MENU_ID_BATCH_SAVE_URLS,
  1799. contexts: defaultContexts,
  1800. title: MENU_BATCH_SAVE_URLS_MESSAGE
  1801. });
  1802. menus.create({
  1803. id: MENU_ID_VIEW_PENDINGS,
  1804. contexts: defaultContexts,
  1805. title: MENU_VIEW_PENDINGS_MESSAGE
  1806. });
  1807. }
  1808. menusCreated = true;
  1809. if (pendingRefresh) {
  1810. pendingRefresh = false;
  1811. (await browser.tabs.query({})).forEach(async tab => await refreshTab$1(tab));
  1812. }
  1813. }
  1814. async function initialize() {
  1815. if (BROWSER_MENUS_API_SUPPORTED) {
  1816. createMenus();
  1817. menus.onClicked.addListener(async (event, tab) => {
  1818. if (event.menuItemId == MENU_ID_SAVE_PAGE) {
  1819. if (event.linkUrl) {
  1820. business$1.saveUrls([event.linkUrl]);
  1821. } else {
  1822. business$1.saveTabs([tab]);
  1823. }
  1824. }
  1825. if (event.menuItemId == MENU_ID_EDIT_AND_SAVE_PAGE) {
  1826. const allTabsData = await getPersistent(tab.id);
  1827. if (allTabsData[tab.id].savedPageDetected) {
  1828. business$1.openEditor(tab);
  1829. } else {
  1830. if (event.linkUrl) {
  1831. business$1.saveUrls([event.linkUrl], { openEditor: true });
  1832. } else {
  1833. business$1.saveTabs([tab], { openEditor: true });
  1834. }
  1835. }
  1836. }
  1837. if (event.menuItemId == MENU_ID_SAVE_SELECTED_LINKS) {
  1838. business$1.saveSelectedLinks(tab);
  1839. }
  1840. if (event.menuItemId == MENU_ID_VIEW_PENDINGS) {
  1841. await browser.tabs.create({ active: true, url: "/src/ui/pages/pendings.html" });
  1842. }
  1843. if (event.menuItemId == MENU_ID_SAVE_SELECTED) {
  1844. business$1.saveTabs([tab], { selected: true });
  1845. }
  1846. if (event.menuItemId == MENU_ID_SAVE_FRAME) {
  1847. business$1.saveTabs([tab], { frameId: event.frameId });
  1848. }
  1849. if (event.menuItemId == MENU_ID_SAVE_SELECTED_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_SELECTED_TABS) {
  1850. const tabs = await queryTabs$1({ currentWindow: true, highlighted: true });
  1851. business$1.saveTabs(tabs);
  1852. }
  1853. if (event.menuItemId == MENU_ID_SAVE_UNPINNED_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_UNPINNED_TABS) {
  1854. const tabs = await queryTabs$1({ currentWindow: true, pinned: false });
  1855. business$1.saveTabs(tabs);
  1856. }
  1857. if (event.menuItemId == MENU_ID_SAVE_ALL_TABS || event.menuItemId == MENU_ID_BUTTON_SAVE_ALL_TABS) {
  1858. const tabs = await queryTabs$1({ currentWindow: true });
  1859. business$1.saveTabs(tabs);
  1860. }
  1861. if (event.menuItemId == MENU_ID_BATCH_SAVE_URLS) {
  1862. business$1.batchSaveUrls();
  1863. }
  1864. if (event.menuItemId == MENU_ID_AUTO_SAVE_TAB) {
  1865. const allTabsData = await getPersistent(tab.id);
  1866. allTabsData[tab.id].autoSave = true;
  1867. await setPersistent(allTabsData);
  1868. refreshExternalComponents(tab);
  1869. }
  1870. if (event.menuItemId == MENU_ID_AUTO_SAVE_DISABLED) {
  1871. const allTabsData = await getPersistent();
  1872. Object.keys(allTabsData).forEach(tabId => {
  1873. if (typeof allTabsData[tabId] == "object" && allTabsData[tabId].autoSave) {
  1874. allTabsData[tabId].autoSave = false;
  1875. }
  1876. });
  1877. allTabsData.autoSaveUnpinned = allTabsData.autoSaveAll = false;
  1878. await setPersistent(allTabsData);
  1879. refreshExternalComponents(tab);
  1880. }
  1881. if (event.menuItemId == MENU_ID_AUTO_SAVE_ALL) {
  1882. const allTabsData = await getPersistent();
  1883. allTabsData.autoSaveAll = event.checked;
  1884. await setPersistent(allTabsData);
  1885. refreshExternalComponents(tab);
  1886. }
  1887. if (event.menuItemId == MENU_ID_AUTO_SAVE_UNPINNED) {
  1888. const allTabsData = await getPersistent();
  1889. allTabsData.autoSaveUnpinned = event.checked;
  1890. await setPersistent(allTabsData);
  1891. refreshExternalComponents(tab);
  1892. }
  1893. if (event.menuItemId.startsWith(MENU_ID_SAVE_WITH_PROFILE_PREFIX)) {
  1894. const profiles = await getProfiles();
  1895. const profileId = event.menuItemId.split(MENU_ID_SAVE_WITH_PROFILE_PREFIX)[1];
  1896. let profileName;
  1897. if (profileId == "default") {
  1898. profileName = DEFAULT_PROFILE_NAME;
  1899. } else {
  1900. const profileIndex = Number(profileId);
  1901. profileName = Object.keys(profiles)[profileIndex];
  1902. }
  1903. profiles[profileName].profileName = profileName;
  1904. business$1.saveTabs([tab], profiles[profileName]);
  1905. }
  1906. if (event.menuItemId.startsWith(MENU_ID_SELECT_PROFILE_PREFIX)) {
  1907. const [profiles, allTabsData] = await Promise.all([getProfiles(), getPersistent()]);
  1908. const profileId = event.menuItemId.split(MENU_ID_SELECT_PROFILE_PREFIX)[1];
  1909. if (profileId == "default") {
  1910. allTabsData.profileName = DEFAULT_PROFILE_NAME;
  1911. } else {
  1912. const profileIndex = Number(profileId);
  1913. allTabsData.profileName = Object.keys(profiles)[profileIndex];
  1914. }
  1915. await setPersistent(allTabsData);
  1916. refreshExternalComponents(tab);
  1917. }
  1918. if (event.menuItemId.startsWith(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX)) {
  1919. const [profiles, rule] = await Promise.all([getProfiles(), getRule(tab.url, true)]);
  1920. const profileId = event.menuItemId.split(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX)[1];
  1921. let profileName;
  1922. if (profileId == "default") {
  1923. profileName = DEFAULT_PROFILE_NAME;
  1924. } else if (profileId == "current") {
  1925. profileName = CURRENT_PROFILE_NAME;
  1926. } else {
  1927. const profileIndex = Number(profileId);
  1928. profileName = Object.keys(profiles)[profileIndex];
  1929. }
  1930. if (rule) {
  1931. await updateRule(rule.url, rule.url, profileName, profileName);
  1932. } else {
  1933. await updateTitleValue(MENU_ID_ASSOCIATE_WITH_PROFILE, MENU_UPDATE_RULE_MESSAGE);
  1934. await addRule(new URL(tab.url).hostname, profileName, profileName);
  1935. }
  1936. }
  1937. });
  1938. if (menusCreated) {
  1939. pendingRefresh = true;
  1940. } else {
  1941. (await browser.tabs.query({})).forEach(async tab => await refreshTab$1(tab));
  1942. }
  1943. }
  1944. }
  1945. async function refreshExternalComponents(tab) {
  1946. const allTabsData = await getPersistent(tab.id);
  1947. await refreshAutoSaveTabs();
  1948. await refreshTab$2(tab);
  1949. try {
  1950. await browser.runtime.sendMessage({ method: "options.refresh", profileName: allTabsData.profileName });
  1951. } catch (error) {
  1952. // ignored
  1953. }
  1954. }
  1955. async function refreshTab$1(tab) {
  1956. if (BROWSER_MENUS_API_SUPPORTED && menusCreated) {
  1957. const promises = [];
  1958. const allTabsData = await getPersistent(tab.id);
  1959. if (allTabsData[tab.id].editorDetected) {
  1960. updateAllVisibleValues(false);
  1961. } else {
  1962. updateAllVisibleValues(true);
  1963. if (AUTO_SAVE_SUPPORTED) {
  1964. promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_DISABLED, !allTabsData[tab.id].autoSave));
  1965. promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_TAB, allTabsData[tab.id].autoSave));
  1966. promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_UNPINNED, Boolean(allTabsData.autoSaveUnpinned)));
  1967. promises.push(updateCheckedValue(MENU_ID_AUTO_SAVE_ALL, Boolean(allTabsData.autoSaveAll)));
  1968. }
  1969. if (tab && tab.url) {
  1970. const options = await getOptions(tab.url);
  1971. promises.push(updateVisibleValue(tab, options.contextMenuEnabled));
  1972. promises.push(updateTitleValue(MENU_ID_EDIT_AND_SAVE_PAGE, allTabsData[tab.id].savedPageDetected ? MENU_EDIT_PAGE_MESSAGE : MENU_EDIT_AND_SAVE_PAGE_MESSAGE));
  1973. if (SELECTABLE_TABS_SUPPORTED) {
  1974. promises.push(menus.update(MENU_ID_SAVE_SELECTED, { visible: !options.saveRawPage }));
  1975. }
  1976. promises.push(menus.update(MENU_ID_EDIT_AND_SAVE_PAGE, { visible: !options.openEditor || allTabsData[tab.id].savedPageDetected }));
  1977. let selectedEntryId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default";
  1978. let title = MENU_CREATE_DOMAIN_RULE_MESSAGE;
  1979. const [profiles, rule] = await Promise.all([getProfiles(), getRule(tab.url)]);
  1980. if (rule) {
  1981. const profileIndex = profileIndexes.get(rule.profile);
  1982. if (profileIndex) {
  1983. selectedEntryId = MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + profileIndex;
  1984. title = MENU_UPDATE_RULE_MESSAGE;
  1985. }
  1986. }
  1987. if (Object.keys(profiles).length > 1) {
  1988. Object.keys(profiles).forEach((profileName, profileIndex) => {
  1989. if (profileName == DEFAULT_PROFILE_NAME) {
  1990. promises.push(updateCheckedValue(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default", selectedEntryId == MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + "default"));
  1991. } else {
  1992. promises.push(updateCheckedValue(MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + profileIndex, selectedEntryId == MENU_ID_ASSOCIATE_WITH_PROFILE_PREFIX + profileIndex));
  1993. }
  1994. });
  1995. promises.push(updateTitleValue(MENU_ID_ASSOCIATE_WITH_PROFILE, title));
  1996. }
  1997. }
  1998. }
  1999. await Promise.all(promises);
  2000. }
  2001. }
  2002. async function updateAllVisibleValues(visible) {
  2003. const lastVisibleState = allMenuVisibleState;
  2004. allMenuVisibleState = visible;
  2005. if (lastVisibleState === undefined || lastVisibleState != visible) {
  2006. const promises = [];
  2007. try {
  2008. MENU_TOP_VISIBLE_ENTRIES.forEach(id => promises.push(menus.update(id, { visible })));
  2009. await Promise.all(promises);
  2010. } catch (error) {
  2011. // ignored
  2012. }
  2013. }
  2014. }
  2015. async function updateVisibleValue(tab, visible) {
  2016. const lastVisibleState = contextMenuVisibleState;
  2017. contextMenuVisibleState = visible;
  2018. if (lastVisibleState === undefined || lastVisibleState != visible) {
  2019. await createMenus(tab);
  2020. }
  2021. }
  2022. function updateTitleValue(id, title) {
  2023. const lastTitleValue = menusTitleState.get(id);
  2024. menusTitleState.set(id, title);
  2025. if (lastTitleValue === undefined) {
  2026. return menus.update(id, { title });
  2027. } else if (lastTitleValue != title) {
  2028. return menus.update(id, { title });
  2029. }
  2030. }
  2031. async function updateCheckedValue(id, checked) {
  2032. checked = Boolean(checked);
  2033. menusCheckedState.set(id, checked);
  2034. await menus.update(id, { checked });
  2035. }
  2036. /*
  2037. * Copyright 2010-2020 Gildas Lormeau
  2038. * contact : gildas.lormeau <at> gmail.com
  2039. *
  2040. * This file is part of SingleFile.
  2041. *
  2042. * The code in this file is free software: you can redistribute it and/or
  2043. * modify it under the terms of the GNU Affero General Public License
  2044. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2045. * of the License, or (at your option) any later version.
  2046. *
  2047. * The code in this file is distributed in the hope that it will be useful,
  2048. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2049. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2050. * General Public License for more details.
  2051. *
  2052. * As additional permission under GNU AGPL version 3 section 7, you may
  2053. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2054. * AGPL normally required by section 4, provided you include this license
  2055. * notice and a URL through which recipients can access the Corresponding
  2056. * Source.
  2057. */
  2058. const commands = browser.commands;
  2059. const BROWSER_COMMANDS_API_SUPPORTED = commands && commands.onCommand && commands.onCommand.addListener;
  2060. let business;
  2061. function init$1(businessApi) {
  2062. business = businessApi;
  2063. }
  2064. if (BROWSER_COMMANDS_API_SUPPORTED) {
  2065. commands.onCommand.addListener(async command => {
  2066. if (command == "save-selected-tabs") {
  2067. const highlightedTabs = await queryTabs$1({ currentWindow: true, highlighted: true });
  2068. business.saveTabs(highlightedTabs, { optionallySelected: true });
  2069. }
  2070. if (command == "save-all-tabs") {
  2071. const tabs = await queryTabs$1({ currentWindow: true });
  2072. business.saveTabs(tabs);
  2073. }
  2074. });
  2075. }
  2076. /*
  2077. * Copyright 2010-2020 Gildas Lormeau
  2078. * contact : gildas.lormeau <at> gmail.com
  2079. *
  2080. * This file is part of SingleFile.
  2081. *
  2082. * The code in this file is free software: you can redistribute it and/or
  2083. * modify it under the terms of the GNU Affero General Public License
  2084. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2085. * of the License, or (at your option) any later version.
  2086. *
  2087. * The code in this file is distributed in the hope that it will be useful,
  2088. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2089. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2090. * General Public License for more details.
  2091. *
  2092. * As additional permission under GNU AGPL version 3 section 7, you may
  2093. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2094. * AGPL normally required by section 4, provided you include this license
  2095. * notice and a URL through which recipients can access the Corresponding
  2096. * Source.
  2097. */
  2098. function init(businessApi) {
  2099. init$2(businessApi);
  2100. init$3(businessApi);
  2101. init$1(businessApi);
  2102. }
  2103. function onMessage$7(message, sender) {
  2104. if (message.method.endsWith(".refreshMenu")) {
  2105. return onMessage$8(message);
  2106. } else {
  2107. return onMessage$9(message, sender);
  2108. }
  2109. }
  2110. async function refreshTab(tab) {
  2111. return Promise.all([createMenus(tab), refreshTab$2(tab)]);
  2112. }
  2113. function onForbiddenDomain(tab) {
  2114. onForbiddenDomain$1(tab);
  2115. }
  2116. function onStart(tabId, step, autoSave) {
  2117. onStart$1(tabId, step, autoSave);
  2118. }
  2119. async function onError(tabId, message, link) {
  2120. onError$1(tabId);
  2121. try {
  2122. if (message) {
  2123. await browser.tabs.sendMessage(tabId, { method: "content.error", error: message.toString(), link });
  2124. }
  2125. } catch (error) {
  2126. // ignored
  2127. }
  2128. }
  2129. function onEdit(tabId) {
  2130. onEdit$1(tabId);
  2131. }
  2132. function onEnd(tabId, autoSave) {
  2133. onEnd$1(tabId, autoSave);
  2134. }
  2135. function onCancelled(tabId) {
  2136. onCancelled$1(tabId);
  2137. }
  2138. function onUploadProgress(tabId, index, maxIndex) {
  2139. onUploadProgress$1(tabId, index, maxIndex);
  2140. }
  2141. function onTabCreated$1(tab) {
  2142. refreshTab$1(tab);
  2143. }
  2144. function onTabActivated$1(tab) {
  2145. refreshTab$1(tab);
  2146. }
  2147. function onInit$3(tab) {
  2148. refreshTab$1(tab);
  2149. }
  2150. /*
  2151. * Copyright 2010-2020 Gildas Lormeau
  2152. * contact : gildas.lormeau <at> gmail.com
  2153. *
  2154. * This file is part of SingleFile.
  2155. *
  2156. * The code in this file is free software: you can redistribute it and/or
  2157. * modify it under the terms of the GNU Affero General Public License
  2158. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2159. * of the License, or (at your option) any later version.
  2160. *
  2161. * The code in this file is distributed in the hope that it will be useful,
  2162. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2163. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2164. * General Public License for more details.
  2165. *
  2166. * As additional permission under GNU AGPL version 3 section 7, you may
  2167. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2168. * AGPL normally required by section 4, provided you include this license
  2169. * notice and a URL through which recipients can access the Corresponding
  2170. * Source.
  2171. */
  2172. /* global browser, fetch, TextDecoder */
  2173. let contentScript, frameScript;
  2174. const contentScriptFiles = [
  2175. "lib/web-stream.js",
  2176. "lib/chrome-browser-polyfill.js",
  2177. "lib/single-file.js"
  2178. ];
  2179. const frameScriptFiles = [
  2180. "lib/chrome-browser-polyfill.js",
  2181. "lib/single-file-frames.js"
  2182. ];
  2183. const basePath = "../../../";
  2184. async function inject(tabId, options) {
  2185. await initScripts(options);
  2186. let scriptsInjected;
  2187. if (!options.removeFrames) {
  2188. try {
  2189. await browser.tabs.executeScript(tabId, { code: frameScript, allFrames: true, matchAboutBlank: true, runAt: "document_start" });
  2190. } catch (error) {
  2191. // ignored
  2192. }
  2193. }
  2194. try {
  2195. await browser.tabs.executeScript(tabId, { code: contentScript, allFrames: false, runAt: "document_idle" });
  2196. scriptsInjected = true;
  2197. } catch (error) {
  2198. // ignored
  2199. }
  2200. if (scriptsInjected) {
  2201. if (options.frameId) {
  2202. await browser.tabs.executeScript(tabId, { code: "document.documentElement.dataset.requestedFrameId = true", frameId: options.frameId, matchAboutBlank: true, runAt: "document_start" });
  2203. }
  2204. }
  2205. return scriptsInjected;
  2206. }
  2207. async function initScripts(options) {
  2208. const extensionScriptFiles = options.extensionScriptFiles || [];
  2209. if (!contentScript && !frameScript) {
  2210. [contentScript, frameScript] = await Promise.all([
  2211. getScript(contentScriptFiles.concat(extensionScriptFiles)),
  2212. getScript(frameScriptFiles)
  2213. ]);
  2214. }
  2215. }
  2216. async function getScript(scriptFiles) {
  2217. const scriptsPromises = scriptFiles.map(async scriptFile => {
  2218. if (typeof scriptFile == "function") {
  2219. return "(" + scriptFile.toString() + ")();";
  2220. } else {
  2221. const scriptResource = await fetch(browser.runtime.getURL(basePath + scriptFile));
  2222. return new TextDecoder().decode(await scriptResource.arrayBuffer());
  2223. }
  2224. });
  2225. let content = "";
  2226. for (const scriptPromise of scriptsPromises) {
  2227. content += await scriptPromise;
  2228. }
  2229. return content;
  2230. }
  2231. /*
  2232. * Copyright 2010-2020 Gildas Lormeau
  2233. * contact : gildas.lormeau <at> gmail.com
  2234. *
  2235. * This file is part of SingleFile.
  2236. *
  2237. * The code in this file is free software: you can redistribute it and/or
  2238. * modify it under the terms of the GNU Affero General Public License
  2239. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2240. * of the License, or (at your option) any later version.
  2241. *
  2242. * The code in this file is distributed in the hope that it will be useful,
  2243. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2244. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2245. * General Public License for more details.
  2246. *
  2247. * As additional permission under GNU AGPL version 3 section 7, you may
  2248. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2249. * AGPL normally required by section 4, provided you include this license
  2250. * notice and a URL through which recipients can access the Corresponding
  2251. * Source.
  2252. */
  2253. /* global browser, window, document, CustomEvent, setTimeout, clearTimeout */
  2254. const FETCH_REQUEST_EVENT = "single-file-request-fetch";
  2255. const FETCH_ACK_EVENT = "single-file-ack-fetch";
  2256. const FETCH_RESPONSE_EVENT = "single-file-response-fetch";
  2257. const ERR_HOST_FETCH = "Host fetch error (SingleFile)";
  2258. const HOST_FETCH_MAX_DELAY = 2500;
  2259. const USE_HOST_FETCH = Boolean(window.wrappedJSObject);
  2260. const fetch$2 = (url, options) => window.fetch(url, options);
  2261. let requestId = 0, pendingResponses = new Map();
  2262. browser.runtime.onMessage.addListener(message => {
  2263. if (message.method == "singlefile.fetchFrame" && window.frameId && window.frameId == message.frameId) {
  2264. return onFetchFrame(message);
  2265. }
  2266. if (message.method == "singlefile.fetchResponse") {
  2267. return onFetchResponse(message);
  2268. }
  2269. });
  2270. async function onFetchFrame(message) {
  2271. try {
  2272. const response = await fetch$2(message.url, { cache: "force-cache", headers: message.headers });
  2273. return {
  2274. status: response.status,
  2275. headers: [...response.headers],
  2276. array: Array.from(new Uint8Array(await response.arrayBuffer()))
  2277. };
  2278. } catch (error) {
  2279. return {
  2280. error: error && error.toString()
  2281. };
  2282. }
  2283. }
  2284. async function onFetchResponse(message) {
  2285. const pendingResponse = pendingResponses.get(message.requestId);
  2286. if (pendingResponse) {
  2287. if (message.error) {
  2288. pendingResponse.reject(new Error(message.error));
  2289. pendingResponses.delete(message.requestId);
  2290. } else {
  2291. if (message.truncated) {
  2292. if (pendingResponse.array) {
  2293. pendingResponse.array = pendingResponse.array.concat(message.array);
  2294. } else {
  2295. pendingResponse.array = message.array;
  2296. pendingResponses.set(message.requestId, pendingResponse);
  2297. }
  2298. if (message.finished) {
  2299. message.array = pendingResponse.array;
  2300. }
  2301. }
  2302. if (!message.truncated || message.finished) {
  2303. pendingResponse.resolve({
  2304. status: message.status,
  2305. headers: { get: headerName => message.headers && message.headers[headerName] },
  2306. arrayBuffer: async () => new Uint8Array(message.array).buffer
  2307. });
  2308. pendingResponses.delete(message.requestId);
  2309. }
  2310. }
  2311. }
  2312. return {};
  2313. }
  2314. async function hostFetch(url, options) {
  2315. const result = new Promise((resolve, reject) => {
  2316. document.dispatchEvent(new CustomEvent(FETCH_REQUEST_EVENT, { detail: JSON.stringify({ url, options }) }));
  2317. document.addEventListener(FETCH_ACK_EVENT, onAckFetch, false);
  2318. document.addEventListener(FETCH_RESPONSE_EVENT, onResponseFetch, false);
  2319. const timeout = setTimeout(() => {
  2320. removeListeners();
  2321. reject(new Error(ERR_HOST_FETCH));
  2322. }, HOST_FETCH_MAX_DELAY);
  2323. function onResponseFetch(event) {
  2324. if (event.detail) {
  2325. if (event.detail.url == url) {
  2326. removeListeners();
  2327. if (event.detail.response) {
  2328. resolve({
  2329. status: event.detail.status,
  2330. headers: new Map(event.detail.headers),
  2331. arrayBuffer: async () => event.detail.response
  2332. });
  2333. } else {
  2334. reject(event.detail.error);
  2335. }
  2336. }
  2337. } else {
  2338. reject();
  2339. }
  2340. }
  2341. function onAckFetch() {
  2342. clearTimeout(timeout);
  2343. }
  2344. function removeListeners() {
  2345. document.removeEventListener(FETCH_RESPONSE_EVENT, onResponseFetch, false);
  2346. document.removeEventListener(FETCH_ACK_EVENT, onAckFetch, false);
  2347. }
  2348. });
  2349. try {
  2350. return await result;
  2351. } catch (error) {
  2352. if (error && error.message == ERR_HOST_FETCH) {
  2353. return fetch$2(url, options);
  2354. } else {
  2355. throw error;
  2356. }
  2357. }
  2358. }
  2359. async function fetchResource(url, options = {}) {
  2360. try {
  2361. const fetchOptions = { cache: "force-cache", headers: options.headers };
  2362. return await (options.referrer && USE_HOST_FETCH ? hostFetch(url, fetchOptions) : fetch$2(url, fetchOptions));
  2363. }
  2364. catch (error) {
  2365. requestId++;
  2366. const promise = new Promise((resolve, reject) => pendingResponses.set(requestId, { resolve, reject }));
  2367. await sendMessage({ method: "singlefile.fetch", url, requestId, referrer: options.referrer, headers: options.headers });
  2368. return promise;
  2369. }
  2370. }
  2371. async function frameFetch(url, options) {
  2372. const response = await sendMessage({ method: "singlefile.fetchFrame", url, frameId: options.frameId, referrer: options.referrer, headers: options.headers });
  2373. return {
  2374. status: response.status,
  2375. headers: new Map(response.headers),
  2376. arrayBuffer: async () => new Uint8Array(response.array).buffer
  2377. };
  2378. }
  2379. async function sendMessage(message) {
  2380. const response = await browser.runtime.sendMessage(message);
  2381. if (!response || response.error) {
  2382. throw new Error(response && response.error && response.error.toString());
  2383. } else {
  2384. return response;
  2385. }
  2386. }
  2387. /*
  2388. * Copyright 2010-2020 Gildas Lormeau
  2389. * contact : gildas.lormeau <at> gmail.com
  2390. *
  2391. * This file is part of SingleFile.
  2392. *
  2393. * The code in this file is free software: you can redistribute it and/or
  2394. * modify it under the terms of the GNU Affero General Public License
  2395. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2396. * of the License, or (at your option) any later version.
  2397. *
  2398. * The code in this file is distributed in the hope that it will be useful,
  2399. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2400. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2401. * General Public License for more details.
  2402. *
  2403. * As additional permission under GNU AGPL version 3 section 7, you may
  2404. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2405. * AGPL normally required by section 4, provided you include this license
  2406. * notice and a URL through which recipients can access the Corresponding
  2407. * Source.
  2408. */
  2409. function injectScript(tabId, options) {
  2410. return inject(tabId, options);
  2411. }
  2412. function getPageData(options, doc, win, initOptions = { fetch: fetchResource, frameFetch }) {
  2413. return globalThis.singlefile.getPageData(options, initOptions, doc, win);
  2414. }
  2415. /*
  2416. * Copyright 2010-2020 Gildas Lormeau
  2417. * contact : gildas.lormeau <at> gmail.com
  2418. *
  2419. * This file is part of SingleFile.
  2420. *
  2421. * The code in this file is free software: you can redistribute it and/or
  2422. * modify it under the terms of the GNU Affero General Public License
  2423. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2424. * of the License, or (at your option) any later version.
  2425. *
  2426. * The code in this file is distributed in the hope that it will be useful,
  2427. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2428. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2429. * General Public License for more details.
  2430. *
  2431. * As additional permission under GNU AGPL version 3 section 7, you may
  2432. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2433. * AGPL normally required by section 4, provided you include this license
  2434. * notice and a URL through which recipients can access the Corresponding
  2435. * Source.
  2436. */
  2437. const ERROR_CONNECTION_ERROR_CHROMIUM = "Could not establish connection. Receiving end does not exist.";
  2438. const ERROR_CONNECTION_LOST_CHROMIUM = "The message port closed before a response was received.";
  2439. const ERROR_CONNECTION_LOST_GECKO = "Message manager disconnected";
  2440. const ERROR_EDITOR_PAGE_CHROMIUM = "Cannot access contents of url ";
  2441. const ERROR_CHANNEL_CLOSED_CHROMIUM = "A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received";
  2442. const INJECT_SCRIPTS_STEP = 1;
  2443. const EXECUTE_SCRIPTS_STEP = 2;
  2444. const TASK_PENDING_STATE = "pending";
  2445. const TASK_PROCESSING_STATE = "processing";
  2446. const extensionScriptFiles = [
  2447. "lib/single-file-extension.js"
  2448. ];
  2449. const tasks = [];
  2450. let currentTaskId = 0, maxParallelWorkers, processInForeground;
  2451. init({ isSavingTab, saveTabs, saveUrls, cancelTab, openEditor, saveSelectedLinks, batchSaveUrls });
  2452. async function saveSelectedLinks(tab) {
  2453. const tabOptions = { extensionScriptFiles, tabId: tab.id, tabIndex: tab.index };
  2454. const scriptsInjected = await injectScript(tab.id, tabOptions);
  2455. if (scriptsInjected) {
  2456. const response = await browser.tabs.sendMessage(tab.id, { method: "content.getSelectedLinks" });
  2457. if (response.urls && response.urls.length) {
  2458. const tab = await batchSaveUrls();
  2459. const onTabUpdated = (tabId, changeInfo) => {
  2460. if (changeInfo.status == "complete" && tabId == tab.id) {
  2461. browser.tabs.onUpdated.removeListener(onTabUpdated);
  2462. browser.tabs.sendMessage(tab.id, { method: "newUrls.addURLs", urls: response.urls });
  2463. }
  2464. };
  2465. browser.tabs.onUpdated.addListener(onTabUpdated);
  2466. }
  2467. } else {
  2468. onForbiddenDomain(tab);
  2469. }
  2470. }
  2471. async function batchSaveUrls() {
  2472. return browser.tabs.create({ active: true, url: "/src/ui/pages/batch-save-urls.html" });
  2473. }
  2474. async function saveUrls(urls, options = {}) {
  2475. await initMaxParallelWorkers();
  2476. await Promise.all(urls.map(async url => {
  2477. const tabOptions = await getOptions(url);
  2478. Object.keys(options).forEach(key => tabOptions[key] = options[key]);
  2479. tabOptions.autoClose = true;
  2480. tabOptions.extensionScriptFiles = extensionScriptFiles;
  2481. if (tabOptions.passReferrerOnError) {
  2482. enableReferrerOnError();
  2483. }
  2484. addTask({
  2485. tab: { url },
  2486. status: TASK_PENDING_STATE,
  2487. options: tabOptions,
  2488. method: "content.save"
  2489. });
  2490. }));
  2491. runTasks();
  2492. }
  2493. async function saveTabs(tabs, options = {}) {
  2494. await initMaxParallelWorkers();
  2495. await Promise.all(tabs.map(async tab => {
  2496. const tabId = tab.id;
  2497. const tabOptions = await getOptions(tab.url);
  2498. Object.keys(options).forEach(key => tabOptions[key] = options[key]);
  2499. tabOptions.tabId = tabId;
  2500. tabOptions.tabIndex = tab.index;
  2501. tabOptions.extensionScriptFiles = extensionScriptFiles;
  2502. if (tabOptions.passReferrerOnError) {
  2503. enableReferrerOnError();
  2504. }
  2505. const tabData = {
  2506. id: tab.id,
  2507. index: tab.index,
  2508. url: tab.url,
  2509. title: tab.title
  2510. };
  2511. if (options.autoSave) {
  2512. if (autoSaveIsEnabled(tab)) {
  2513. const taskInfo = addTask({
  2514. status: TASK_PROCESSING_STATE,
  2515. tab: tabData,
  2516. options: tabOptions,
  2517. method: "content.autosave"
  2518. });
  2519. runTask(taskInfo);
  2520. }
  2521. } else {
  2522. onStart(tabId, INJECT_SCRIPTS_STEP);
  2523. const scriptsInjected = await injectScript(tabId, tabOptions);
  2524. if (scriptsInjected || isEditor(tab)) {
  2525. onStart(tabId, EXECUTE_SCRIPTS_STEP);
  2526. addTask({
  2527. status: TASK_PENDING_STATE,
  2528. tab: tabData,
  2529. options: tabOptions,
  2530. method: "content.save"
  2531. });
  2532. } else {
  2533. onForbiddenDomain(tab);
  2534. }
  2535. }
  2536. }));
  2537. runTasks();
  2538. }
  2539. function addTask(info) {
  2540. const taskInfo = {
  2541. id: currentTaskId,
  2542. status: info.status,
  2543. tab: info.tab,
  2544. options: info.options,
  2545. method: info.method,
  2546. done: function (runNextTasks = true) {
  2547. const index = tasks.findIndex(taskInfo => taskInfo.id == this.id);
  2548. if (index > -1) {
  2549. tasks.splice(index, 1);
  2550. if (runNextTasks) {
  2551. runTasks();
  2552. }
  2553. }
  2554. }
  2555. };
  2556. tasks.push(taskInfo);
  2557. currentTaskId++;
  2558. return taskInfo;
  2559. }
  2560. function openEditor(tab) {
  2561. browser.tabs.sendMessage(tab.id, { method: "content.openEditor" });
  2562. }
  2563. async function initMaxParallelWorkers() {
  2564. if (!maxParallelWorkers) {
  2565. const configData = await getConfig();
  2566. processInForeground = configData.processInForeground;
  2567. maxParallelWorkers = processInForeground ? 1 : configData.maxParallelWorkers;
  2568. }
  2569. }
  2570. function runTasks() {
  2571. const processingCount = tasks.filter(taskInfo => taskInfo.status == TASK_PROCESSING_STATE).length;
  2572. for (let index = 0; index < Math.min(tasks.length - processingCount, (maxParallelWorkers - processingCount)); index++) {
  2573. const taskInfo = tasks.find(taskInfo => taskInfo.status == TASK_PENDING_STATE);
  2574. if (taskInfo) {
  2575. runTask(taskInfo);
  2576. }
  2577. }
  2578. }
  2579. async function runTask(taskInfo) {
  2580. const taskId = taskInfo.id;
  2581. taskInfo.status = TASK_PROCESSING_STATE;
  2582. if (!taskInfo.tab.id) {
  2583. let scriptsInjected;
  2584. try {
  2585. const tab = await createTabAndWaitUntilComplete({ url: taskInfo.tab.url, active: false });
  2586. taskInfo.tab.id = taskInfo.options.tabId = tab.id;
  2587. taskInfo.tab.index = taskInfo.options.tabIndex = tab.index;
  2588. onStart(taskInfo.tab.id, INJECT_SCRIPTS_STEP);
  2589. scriptsInjected = await injectScript(taskInfo.tab.id, taskInfo.options);
  2590. } catch (tabId) {
  2591. taskInfo.tab.id = tabId;
  2592. }
  2593. if (scriptsInjected) {
  2594. onStart(taskInfo.tab.id, EXECUTE_SCRIPTS_STEP);
  2595. } else {
  2596. taskInfo.done();
  2597. return;
  2598. }
  2599. }
  2600. taskInfo.options.taskId = taskId;
  2601. try {
  2602. if (processInForeground) {
  2603. await browser.tabs.update(taskInfo.tab.id, { active: true });
  2604. }
  2605. await browser.tabs.sendMessage(taskInfo.tab.id, { method: taskInfo.method, options: taskInfo.options });
  2606. } catch (error) {
  2607. if (error && (!error.message || !isIgnoredError(error))) {
  2608. console.log(error.message ? error.message : error); // eslint-disable-line no-console
  2609. onError(taskInfo.tab.id, error.message, error.link);
  2610. taskInfo.done();
  2611. }
  2612. }
  2613. }
  2614. function isIgnoredError(error) {
  2615. return error.message == ERROR_CONNECTION_LOST_CHROMIUM ||
  2616. error.message == ERROR_CONNECTION_ERROR_CHROMIUM ||
  2617. error.message == ERROR_CONNECTION_LOST_GECKO ||
  2618. error.message == ERROR_CHANNEL_CLOSED_CHROMIUM ||
  2619. error.message.startsWith(ERROR_EDITOR_PAGE_CHROMIUM + JSON.stringify(EDITOR_URL));
  2620. }
  2621. function isSavingTab(tab) {
  2622. return Boolean(tasks.find(taskInfo => taskInfo.tab.id == tab.id));
  2623. }
  2624. function onInit$2(tab) {
  2625. cancelTab(tab.id, false);
  2626. }
  2627. function onTabReplaced$2(addedTabId, removedTabId) {
  2628. tasks.forEach(taskInfo => {
  2629. if (taskInfo.tab.id == removedTabId) {
  2630. taskInfo.tab.id = addedTabId;
  2631. }
  2632. });
  2633. }
  2634. function onSaveEnd(taskId) {
  2635. const taskInfo = tasks.find(taskInfo => taskInfo.id == taskId);
  2636. if (taskInfo) {
  2637. if (taskInfo.options.autoClose && !taskInfo.cancelled) {
  2638. browser.tabs.remove(taskInfo.tab.id);
  2639. }
  2640. taskInfo.done();
  2641. }
  2642. }
  2643. async function createTabAndWaitUntilComplete(createProperties) {
  2644. const tab = await browser.tabs.create(createProperties);
  2645. return new Promise((resolve, reject) => {
  2646. browser.tabs.onUpdated.addListener(onTabUpdated);
  2647. browser.tabs.onRemoved.addListener(onTabRemoved);
  2648. function onTabUpdated(tabId, changeInfo) {
  2649. if (tabId == tab.id && changeInfo.status == "complete") {
  2650. resolve(tab);
  2651. browser.tabs.onUpdated.removeListener(onTabUpdated);
  2652. browser.tabs.onRemoved.removeListener(onTabRemoved);
  2653. }
  2654. }
  2655. function onTabRemoved(tabId) {
  2656. if (tabId == tab.id) {
  2657. reject(tabId);
  2658. browser.tabs.onRemoved.removeListener(onTabRemoved);
  2659. }
  2660. }
  2661. });
  2662. }
  2663. function setCancelCallback(taskId, cancelCallback) {
  2664. const taskInfo = tasks.find(taskInfo => taskInfo.id == taskId);
  2665. if (taskInfo) {
  2666. taskInfo.cancel = cancelCallback;
  2667. }
  2668. }
  2669. function cancelTab(tabId, stopProcessing = true) {
  2670. Array.from(tasks).filter(taskInfo =>
  2671. taskInfo.tab.id == tabId &&
  2672. !taskInfo.options.autoSave &&
  2673. (stopProcessing || taskInfo.status != TASK_PROCESSING_STATE)
  2674. ).forEach(cancel);
  2675. }
  2676. function cancelTask(taskId) {
  2677. cancel(tasks.find(taskInfo => taskInfo.id == taskId));
  2678. }
  2679. function cancelAllTasks() {
  2680. Array.from(tasks).forEach(taskInfo => cancel(taskInfo, false));
  2681. }
  2682. function getTasksInfo() {
  2683. return tasks.map(mapTaskInfo);
  2684. }
  2685. function getTaskInfo(taskId) {
  2686. return tasks.find(taskInfo => taskInfo.id == taskId);
  2687. }
  2688. function cancel(taskInfo, runNextTasks) {
  2689. const tabId = taskInfo.tab.id;
  2690. taskInfo.cancelled = true;
  2691. if (tabId) {
  2692. browser.tabs.sendMessage(tabId, {
  2693. method: "content.cancelSave",
  2694. options: {
  2695. loadDeferredImages: taskInfo.options.loadDeferredImages,
  2696. loadDeferredImagesKeepZoomLevel: taskInfo.options.loadDeferredImagesKeepZoomLevel
  2697. }
  2698. }).catch(() => {
  2699. // ignored
  2700. });
  2701. if (taskInfo.method == "content.autosave") {
  2702. onEnd(tabId, true);
  2703. }
  2704. onCancelled(taskInfo.tab);
  2705. }
  2706. if (taskInfo.cancel) {
  2707. taskInfo.cancel();
  2708. }
  2709. taskInfo.done(runNextTasks);
  2710. }
  2711. function mapTaskInfo(taskInfo) {
  2712. return { id: taskInfo.id, tabId: taskInfo.tab.id, index: taskInfo.tab.index, url: taskInfo.tab.url, title: taskInfo.tab.title, cancelled: taskInfo.cancelled, status: taskInfo.status };
  2713. }
  2714. /*
  2715. * Copyright 2010-2020 Gildas Lormeau
  2716. * contact : gildas.lormeau <at> gmail.com
  2717. *
  2718. * This file is part of SingleFile.
  2719. *
  2720. * The code in this file is free software: you can redistribute it and/or
  2721. * modify it under the terms of the GNU Affero General Public License
  2722. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2723. * of the License, or (at your option) any later version.
  2724. *
  2725. * The code in this file is distributed in the hope that it will be useful,
  2726. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2727. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2728. * General Public License for more details.
  2729. *
  2730. * As additional permission under GNU AGPL version 3 section 7, you may
  2731. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2732. * AGPL normally required by section 4, provided you include this license
  2733. * notice and a URL through which recipients can access the Corresponding
  2734. * Source.
  2735. */
  2736. /* global browser */
  2737. let enabled = true;
  2738. async function onMessage$6(message) {
  2739. if (message.method.endsWith(".state")) {
  2740. return { enabled };
  2741. }
  2742. }
  2743. async function externalSave(pageData) {
  2744. pageData.autoSaveExternalSave = false;
  2745. let response;
  2746. try {
  2747. response = await browser.runtime.sendNativeMessage("singlefile_companion", {
  2748. method: "externalSave",
  2749. pageData
  2750. });
  2751. } catch (error) {
  2752. if (!error.message || !error.message.includes("Native host has exited")) {
  2753. throw error;
  2754. }
  2755. }
  2756. if (response && response.error) {
  2757. throw new Error(response.error + " (Companion)");
  2758. }
  2759. }
  2760. async function save(pageData) {
  2761. let response;
  2762. try {
  2763. response = await browser.runtime.sendNativeMessage("singlefile_companion", {
  2764. method: "save",
  2765. pageData
  2766. });
  2767. } catch (error) {
  2768. if (!error.message || !error.message.includes("Native host has exited")) {
  2769. throw error;
  2770. }
  2771. }
  2772. if (response && response.error) {
  2773. throw new Error(response.error + " (Companion)");
  2774. }
  2775. }
  2776. /*
  2777. * Copyright 2010-2020 Gildas Lormeau
  2778. * contact : gildas.lormeau <at> gmail.com
  2779. *
  2780. * This file is part of SingleFile.
  2781. *
  2782. * The code in this file is free software: you can redistribute it and/or
  2783. * modify it under the terms of the GNU Affero General Public License
  2784. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2785. * of the License, or (at your option) any later version.
  2786. *
  2787. * The code in this file is distributed in the hope that it will be useful,
  2788. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2789. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2790. * General Public License for more details.
  2791. *
  2792. * As additional permission under GNU AGPL version 3 section 7, you may
  2793. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2794. * AGPL normally required by section 4, provided you include this license
  2795. * notice and a URL through which recipients can access the Corresponding
  2796. * Source.
  2797. */
  2798. const pendingSaves = new Set();
  2799. Promise.resolve().then(enable);
  2800. async function onMessage$5(message) {
  2801. if (message.method.endsWith(".saveCreatedBookmarks")) {
  2802. enable();
  2803. return {};
  2804. }
  2805. if (message.method.endsWith(".disable")) {
  2806. disable();
  2807. return {};
  2808. }
  2809. }
  2810. async function enable() {
  2811. try {
  2812. browser.bookmarks.onCreated.removeListener(onCreated);
  2813. browser.bookmarks.onMoved.removeListener(onMoved);
  2814. } catch (error) {
  2815. // ignored
  2816. }
  2817. let enabled;
  2818. const profiles = await getProfiles();
  2819. Object.keys(profiles).forEach(profileName => {
  2820. if (profiles[profileName].saveCreatedBookmarks) {
  2821. enabled = true;
  2822. }
  2823. });
  2824. if (enabled) {
  2825. browser.bookmarks.onCreated.addListener(onCreated);
  2826. browser.bookmarks.onMoved.addListener(onMoved);
  2827. }
  2828. }
  2829. async function disable() {
  2830. let disabled;
  2831. const profiles = await getProfiles();
  2832. Object.keys(profiles).forEach(profileName => disabled = disabled || !profiles[profileName].saveCreatedBookmarks);
  2833. if (disabled) {
  2834. browser.bookmarks.onCreated.removeListener(onCreated);
  2835. browser.bookmarks.onMoved.removeListener(onMoved);
  2836. }
  2837. }
  2838. async function update(id, changes) {
  2839. try {
  2840. await browser.bookmarks.update(id, changes);
  2841. } catch (error) {
  2842. // ignored
  2843. }
  2844. }
  2845. async function onCreated(bookmarkId, bookmarkInfo) {
  2846. pendingSaves.add(bookmarkId);
  2847. await saveBookmark(bookmarkId, bookmarkInfo.url, bookmarkInfo);
  2848. }
  2849. async function onMoved(bookmarkId, bookmarkInfo) {
  2850. if (pendingSaves.has(bookmarkId)) {
  2851. const bookmarks = await browser.bookmarks.get(bookmarkId);
  2852. if (bookmarks[0]) {
  2853. await saveBookmark(bookmarkId, bookmarks[0].url, bookmarkInfo);
  2854. }
  2855. }
  2856. }
  2857. async function saveBookmark(bookmarkId, url, bookmarkInfo) {
  2858. const activeTabs = await browser.tabs.query({ lastFocusedWindow: true, active: true });
  2859. const options = await getOptions(url);
  2860. if (options.saveCreatedBookmarks) {
  2861. const bookmarkFolders = await getParentFolders(bookmarkInfo.parentId);
  2862. const allowedBookmarkSet = options.allowedBookmarkFolders.toString();
  2863. const allowedBookmark = bookmarkFolders.find(folder => options.allowedBookmarkFolders.includes(folder));
  2864. const ignoredBookmarkSet = options.ignoredBookmarkFolders.toString();
  2865. const ignoredBookmark = bookmarkFolders.find(folder => options.ignoredBookmarkFolders.includes(folder));
  2866. if (
  2867. ((allowedBookmarkSet && allowedBookmark) || !allowedBookmarkSet) &&
  2868. ((ignoredBookmarkSet && !ignoredBookmark) || !ignoredBookmarkSet)
  2869. ) {
  2870. if (activeTabs.length && activeTabs[0].url == url) {
  2871. pendingSaves.delete(bookmarkId);
  2872. saveTabs(activeTabs, { bookmarkId, bookmarkFolders });
  2873. } else {
  2874. const tabs = await browser.tabs.query({});
  2875. if (tabs.length) {
  2876. const tab = tabs.find(tab => tab.url == url);
  2877. if (tab) {
  2878. pendingSaves.delete(bookmarkId);
  2879. saveTabs([tab], { bookmarkId, bookmarkFolders });
  2880. } else {
  2881. if (url) {
  2882. if (url == "about:blank") {
  2883. browser.bookmarks.onChanged.addListener(onChanged);
  2884. } else {
  2885. saveUrl(url);
  2886. }
  2887. }
  2888. }
  2889. }
  2890. }
  2891. }
  2892. }
  2893. async function getParentFolders(id, folderNames = []) {
  2894. if (id) {
  2895. const bookmarkNode = (await browser.bookmarks.get(id))[0];
  2896. if (bookmarkNode && bookmarkNode.title) {
  2897. folderNames.unshift(bookmarkNode.title);
  2898. await getParentFolders(bookmarkNode.parentId, folderNames);
  2899. }
  2900. }
  2901. return folderNames;
  2902. }
  2903. function onChanged(id, changeInfo) {
  2904. if (id == bookmarkId && changeInfo.url) {
  2905. browser.bookmarks.onChanged.removeListener(onChanged);
  2906. saveUrl(changeInfo.url);
  2907. }
  2908. }
  2909. function saveUrl(url) {
  2910. pendingSaves.delete(bookmarkId);
  2911. saveUrls([url], { bookmarkId });
  2912. }
  2913. }
  2914. /*
  2915. * Copyright 2010-2020 Gildas Lormeau
  2916. * contact : gildas.lormeau <at> gmail.com
  2917. *
  2918. * This file is part of SingleFile.
  2919. *
  2920. * The code in this file is free software: you can redistribute it and/or
  2921. * modify it under the terms of the GNU Affero General Public License
  2922. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2923. * of the License, or (at your option) any later version.
  2924. *
  2925. * The code in this file is distributed in the hope that it will be useful,
  2926. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2927. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2928. * General Public License for more details.
  2929. *
  2930. * As additional permission under GNU AGPL version 3 section 7, you may
  2931. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2932. * AGPL normally required by section 4, provided you include this license
  2933. * notice and a URL through which recipients can access the Corresponding
  2934. * Source.
  2935. */
  2936. /* global fetch */
  2937. const urlService = "https://api.woleet.io/v1/anchor";
  2938. const apiKey = "";
  2939. async function anchor(hash, userKey) {
  2940. let bearer = userKey || apiKey;
  2941. const response = await fetch(urlService, {
  2942. method: "POST",
  2943. headers: {
  2944. "Accept": "application/json",
  2945. "Content-Type": "application/json",
  2946. "Authorization": "Bearer " + bearer
  2947. },
  2948. body: JSON.stringify({
  2949. "name": hash,
  2950. "hash": hash,
  2951. "public": true
  2952. })
  2953. });
  2954. if (response.status == 401) {
  2955. const error = new Error("Your access token on Woleet is invalid. Go to __DOC_LINK__ to create your account.");
  2956. error.link = "https://app.woleet.io/";
  2957. throw error;
  2958. } else if (response.status == 402) {
  2959. const error = new Error("You have no more credits on Woleet. Go to __DOC_LINK__ to recharge them.");
  2960. error.link = "https://app.woleet.io/";
  2961. throw error;
  2962. } else if (response.status >= 400) {
  2963. throw new Error((response.statusText || ("Error " + response.status)) + " (Woleet)");
  2964. }
  2965. return response.json();
  2966. }
  2967. /*
  2968. * Copyright 2010-2020 Gildas Lormeau
  2969. * contact : gildas.lormeau <at> gmail.com
  2970. *
  2971. * This file is part of SingleFile.
  2972. *
  2973. * The code in this file is free software: you can redistribute it and/or
  2974. * modify it under the terms of the GNU Affero General Public License
  2975. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  2976. * of the License, or (at your option) any later version.
  2977. *
  2978. * The code in this file is distributed in the hope that it will be useful,
  2979. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  2980. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  2981. * General Public License for more details.
  2982. *
  2983. * As additional permission under GNU AGPL version 3 section 7, you may
  2984. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  2985. * AGPL normally required by section 4, provided you include this license
  2986. * notice and a URL through which recipients can access the Corresponding
  2987. * Source.
  2988. */
  2989. /* global browser, fetch, setInterval, URLSearchParams, URL */
  2990. const TOKEN_URL$1 = "https://oauth2.googleapis.com/token";
  2991. const AUTH_URL$1 = "https://accounts.google.com/o/oauth2/v2/auth";
  2992. const REVOKE_ACCESS_URL$1 = "https://accounts.google.com/o/oauth2/revoke";
  2993. const GDRIVE_URL = "https://www.googleapis.com/drive/v3/files";
  2994. const GDRIVE_UPLOAD_URL = "https://www.googleapis.com/upload/drive/v3/files";
  2995. const CONFLICT_ACTION_UNIQUIFY$4 = "uniquify";
  2996. const CONFLICT_ACTION_OVERWRITE$3 = "overwrite";
  2997. const CONFLICT_ACTION_SKIP$4 = "skip";
  2998. const CONFLICT_ACTION_PROMPT$3 = "prompt";
  2999. class GDrive {
  3000. constructor(clientId, clientKey, scopes) {
  3001. this.clientId = clientId;
  3002. this.clientKey = clientKey;
  3003. this.scopes = scopes;
  3004. this.folderIds = new Map();
  3005. setInterval(() => this.folderIds.clear(), 60 * 1000);
  3006. }
  3007. async auth(options = { interactive: true }) {
  3008. if (nativeAuth(options)) {
  3009. this.accessToken = await browser.identity.getAuthToken({ interactive: options.interactive });
  3010. return { revokableAccessToken: this.accessToken };
  3011. } else {
  3012. this.authURL = AUTH_URL$1 +
  3013. "?client_id=" + this.clientId +
  3014. "&response_type=code" +
  3015. "&access_type=offline" +
  3016. "&redirect_uri=" + browser.identity.getRedirectURL() +
  3017. "&scope=" + this.scopes.join(" ");
  3018. return options.code ? authFromCode$1(this, options) : initAuth$1(this, options);
  3019. }
  3020. }
  3021. setAuthInfo(authInfo, options) {
  3022. if (!nativeAuth(options)) {
  3023. if (authInfo) {
  3024. this.accessToken = authInfo.accessToken;
  3025. this.refreshToken = authInfo.refreshToken;
  3026. this.expirationDate = authInfo.expirationDate;
  3027. } else {
  3028. delete this.accessToken;
  3029. delete this.refreshToken;
  3030. delete this.expirationDate;
  3031. }
  3032. }
  3033. }
  3034. async refreshAuthToken() {
  3035. if (this.refreshToken) {
  3036. const httpResponse = await fetch(TOKEN_URL$1, {
  3037. method: "POST",
  3038. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  3039. body: "client_id=" + this.clientId +
  3040. "&refresh_token=" + this.refreshToken +
  3041. "&grant_type=refresh_token" +
  3042. "&client_secret=" + this.clientKey
  3043. });
  3044. if (httpResponse.status == 400) {
  3045. throw new Error("unknown_token");
  3046. }
  3047. const response = await getJSON$1(httpResponse);
  3048. this.accessToken = response.access_token;
  3049. if (response.refresh_token) {
  3050. this.refreshToken = response.refresh_token;
  3051. }
  3052. if (response.expires_in) {
  3053. this.expirationDate = Date.now() + (response.expires_in * 1000);
  3054. }
  3055. return { accessToken: this.accessToken, refreshToken: this.refreshToken, expirationDate: this.expirationDate };
  3056. } else {
  3057. try {
  3058. if (browser.identity && browser.identity.removeCachedAuthToken && this.accessToken) {
  3059. await browser.identity.removeCachedAuthToken({ token: this.accessToken });
  3060. }
  3061. this.accessToken = await browser.identity.getAuthToken({ interactive: false });
  3062. return { revokableAccessToken: this.accessToken };
  3063. } catch (error) {
  3064. delete this.accessToken;
  3065. }
  3066. }
  3067. }
  3068. async revokeAuthToken(accessToken) {
  3069. if (accessToken) {
  3070. if (browser.identity && browser.identity.removeCachedAuthToken) {
  3071. try {
  3072. await browser.identity.removeCachedAuthToken({ token: accessToken });
  3073. } catch (error) {
  3074. // ignored
  3075. }
  3076. }
  3077. const httpResponse = await fetch(REVOKE_ACCESS_URL$1, {
  3078. method: "POST",
  3079. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  3080. body: "token=" + accessToken
  3081. });
  3082. try {
  3083. await getJSON$1(httpResponse);
  3084. }
  3085. catch (error) {
  3086. if (error.message != "invalid_token") {
  3087. throw error;
  3088. }
  3089. }
  3090. finally {
  3091. delete this.accessToken;
  3092. delete this.refreshToken;
  3093. delete this.expirationDate;
  3094. }
  3095. }
  3096. }
  3097. async upload(fullFilename, blob, options, setCancelCallback, retry = true) {
  3098. const parentFolderId = await getParentFolderId(this, fullFilename);
  3099. const fileParts = fullFilename.split("/");
  3100. const filename = fileParts.pop();
  3101. const uploader = new MediaUploader$1({
  3102. token: this.accessToken,
  3103. file: blob,
  3104. parents: [parentFolderId],
  3105. filename,
  3106. onProgress: options.onProgress,
  3107. filenameConflictAction: options.filenameConflictAction,
  3108. prompt: options.prompt
  3109. });
  3110. try {
  3111. if (setCancelCallback) {
  3112. setCancelCallback(() => uploader.cancelled = true);
  3113. }
  3114. await uploader.upload();
  3115. }
  3116. catch (error) {
  3117. if (error.message == "path_not_found" && retry) {
  3118. this.folderIds.clear();
  3119. return this.upload(fullFilename, blob, options, setCancelCallback);
  3120. } else {
  3121. throw error;
  3122. }
  3123. }
  3124. }
  3125. }
  3126. class MediaUploader$1 {
  3127. constructor(options) {
  3128. this.file = options.file;
  3129. this.onProgress = options.onProgress;
  3130. this.contentType = this.file.type || "application/octet-stream";
  3131. this.metadata = {
  3132. name: options.filename,
  3133. mimeType: this.contentType,
  3134. parents: options.parents || ["root"]
  3135. };
  3136. this.token = options.token;
  3137. this.offset = 0;
  3138. this.chunkSize = options.chunkSize || 512 * 1024;
  3139. this.filenameConflictAction = options.filenameConflictAction;
  3140. this.prompt = options.prompt;
  3141. }
  3142. async upload(indexFilename = 1) {
  3143. let method = "POST";
  3144. let fileId;
  3145. const httpListResponse = getResponse$1(await fetch(GDRIVE_URL + `?q=name = '${this.metadata.name}' and trashed != true and '${this.metadata.parents[0]}' in parents`, {
  3146. headers: {
  3147. "Authorization": "Bearer " + this.token,
  3148. "Content-Type": "application/json"
  3149. }
  3150. }));
  3151. const response = await httpListResponse.json();
  3152. if (response.files.length) {
  3153. if (this.filenameConflictAction == CONFLICT_ACTION_OVERWRITE$3) {
  3154. method = "PATCH";
  3155. fileId = response.files[0].id;
  3156. this.metadata.parents = null;
  3157. } else if (this.filenameConflictAction == CONFLICT_ACTION_UNIQUIFY$4) {
  3158. let nameWithoutExtension = this.metadata.name;
  3159. let extension = "";
  3160. const dotIndex = this.metadata.name.lastIndexOf(".");
  3161. if (dotIndex > -1) {
  3162. nameWithoutExtension = this.metadata.name.substring(0, dotIndex);
  3163. extension = this.metadata.name.substring(dotIndex + 1);
  3164. }
  3165. const name = nameWithoutExtension + " (" + indexFilename + ")." + extension;
  3166. const httpResponse = getResponse$1(await fetch(GDRIVE_URL + `?q=name = '${name}' and trashed != true and '${this.metadata.parents[0]}' in parents`, {
  3167. headers: {
  3168. "Authorization": "Bearer " + this.token,
  3169. "Content-Type": "application/json"
  3170. }
  3171. }));
  3172. const response = await httpResponse.json();
  3173. if (response.files.length) {
  3174. return this.upload(indexFilename + 1);
  3175. } else {
  3176. this.metadata.name = name;
  3177. }
  3178. } else if (this.filenameConflictAction == CONFLICT_ACTION_PROMPT$3) {
  3179. if (this.prompt) {
  3180. const name = await this.prompt(this.metadata.name);
  3181. if (name) {
  3182. this.metadata.name = name;
  3183. return this.upload(indexFilename);
  3184. } else {
  3185. return response;
  3186. }
  3187. } else {
  3188. this.filenameConflictAction = CONFLICT_ACTION_UNIQUIFY$4;
  3189. return this.upload(indexFilename);
  3190. }
  3191. } else if (this.filenameConflictAction == CONFLICT_ACTION_SKIP$4) {
  3192. return response;
  3193. }
  3194. }
  3195. const httpResponse = getResponse$1(await fetch(GDRIVE_UPLOAD_URL + (fileId ? "/" + fileId : "") + "?uploadType=resumable", {
  3196. method,
  3197. headers: {
  3198. "Authorization": "Bearer " + this.token,
  3199. "Content-Type": "application/json",
  3200. "X-Upload-Content-Length": this.file.size,
  3201. "X-Upload-Content-Type": this.contentType
  3202. },
  3203. body: JSON.stringify(this.metadata)
  3204. }));
  3205. const location = httpResponse.headers.get("Location");
  3206. this.url = location;
  3207. if (!this.cancelled) {
  3208. if (this.onProgress) {
  3209. this.onProgress(0, this.file.size);
  3210. }
  3211. return sendFile$1(this);
  3212. }
  3213. }
  3214. }
  3215. async function authFromCode$1(gdrive, options) {
  3216. const httpResponse = await fetch(TOKEN_URL$1, {
  3217. method: "POST",
  3218. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  3219. body: "client_id=" + gdrive.clientId +
  3220. "&client_secret=" + gdrive.clientKey +
  3221. "&grant_type=authorization_code" +
  3222. "&code=" + options.code +
  3223. "&redirect_uri=" + browser.identity.getRedirectURL()
  3224. });
  3225. const response = await getJSON$1(httpResponse);
  3226. gdrive.accessToken = response.access_token;
  3227. gdrive.refreshToken = response.refresh_token;
  3228. gdrive.expirationDate = Date.now() + (response.expires_in * 1000);
  3229. return { accessToken: gdrive.accessToken, refreshToken: gdrive.refreshToken, expirationDate: gdrive.expirationDate };
  3230. }
  3231. async function initAuth$1(gdrive, options) {
  3232. let code;
  3233. try {
  3234. if (browser.identity && browser.identity.launchWebAuthFlow && !options.forceWebAuthFlow) {
  3235. const authURL = await browser.identity.launchWebAuthFlow({
  3236. interactive: options.interactive,
  3237. url: gdrive.authURL
  3238. });
  3239. options.code = new URLSearchParams(new URL(authURL).search).get("code");
  3240. return await authFromCode$1(gdrive, options);
  3241. } else if (options.launchWebAuthFlow) {
  3242. options.extractAuthCode(browser.identity.getRedirectURL())
  3243. .then(authCode => code = authCode)
  3244. .catch(() => { /* ignored */ });
  3245. return await options.launchWebAuthFlow({ url: gdrive.authURL });
  3246. } else {
  3247. throw new Error("auth_not_supported");
  3248. }
  3249. }
  3250. catch (error) {
  3251. if (error.message && (error.message == "code_required" || error.message.includes("access"))) {
  3252. if (code) {
  3253. options.code = code;
  3254. return await authFromCode$1(gdrive, options);
  3255. } else {
  3256. throw new Error("code_required");
  3257. }
  3258. } else {
  3259. throw error;
  3260. }
  3261. }
  3262. }
  3263. function nativeAuth(options = {}) {
  3264. return Boolean(browser.identity && browser.identity.getAuthToken) && !options.forceWebAuthFlow;
  3265. }
  3266. async function getParentFolderId(gdrive, filename, retry = true) {
  3267. const fileParts = filename.split("/");
  3268. fileParts.pop();
  3269. const folderId = gdrive.folderIds.get(fileParts.join("/"));
  3270. if (folderId) {
  3271. return folderId;
  3272. }
  3273. let parentFolderId = "root";
  3274. if (fileParts.length) {
  3275. let fullFolderName = "";
  3276. for (const folderName of fileParts) {
  3277. if (fullFolderName) {
  3278. fullFolderName += "/";
  3279. }
  3280. fullFolderName += folderName;
  3281. const folderId = gdrive.folderIds.get(fullFolderName);
  3282. if (folderId) {
  3283. parentFolderId = folderId;
  3284. } else {
  3285. try {
  3286. parentFolderId = await getOrCreateFolder(gdrive, folderName, parentFolderId);
  3287. gdrive.folderIds.set(fullFolderName, parentFolderId);
  3288. } catch (error) {
  3289. if (error.message == "path_not_found" && retry) {
  3290. gdrive.folderIds.clear();
  3291. return getParentFolderId(gdrive, filename, false);
  3292. } else {
  3293. throw error;
  3294. }
  3295. }
  3296. }
  3297. }
  3298. }
  3299. return parentFolderId;
  3300. }
  3301. async function getOrCreateFolder(gdrive, folderName, parentFolderId) {
  3302. const response = await getFolder(gdrive, folderName, parentFolderId);
  3303. if (response.files.length) {
  3304. return response.files[0].id;
  3305. } else {
  3306. const response = await createFolder(gdrive, folderName, parentFolderId);
  3307. return response.id;
  3308. }
  3309. }
  3310. async function getFolder(gdrive, folderName, parentFolderId) {
  3311. const httpResponse = await fetch(GDRIVE_URL + "?q=mimeType = 'application/vnd.google-apps.folder' and name = '" + folderName + "' and trashed != true and '" + parentFolderId + "' in parents", {
  3312. headers: {
  3313. "Authorization": "Bearer " + gdrive.accessToken
  3314. }
  3315. });
  3316. return getJSON$1(httpResponse);
  3317. }
  3318. async function createFolder(gdrive, folderName, parentFolderId) {
  3319. const httpResponse = await fetch(GDRIVE_URL, {
  3320. method: "POST",
  3321. headers: {
  3322. "Authorization": "Bearer " + gdrive.accessToken,
  3323. "Content-Type": "application/json"
  3324. },
  3325. body: JSON.stringify({
  3326. name: folderName,
  3327. parents: [parentFolderId],
  3328. mimeType: "application/vnd.google-apps.folder"
  3329. })
  3330. });
  3331. return getJSON$1(httpResponse);
  3332. }
  3333. async function sendFile$1(mediaUploader) {
  3334. let content = mediaUploader.file, end = mediaUploader.file.size;
  3335. if (mediaUploader.offset || mediaUploader.chunkSize) {
  3336. if (mediaUploader.chunkSize) {
  3337. end = Math.min(mediaUploader.offset + mediaUploader.chunkSize, mediaUploader.file.size);
  3338. }
  3339. content = content.slice(mediaUploader.offset, end);
  3340. }
  3341. const httpResponse = await fetch(mediaUploader.url, {
  3342. method: "PUT",
  3343. headers: {
  3344. "Authorization": "Bearer " + mediaUploader.token,
  3345. "Content-Type": mediaUploader.contentType,
  3346. "Content-Range": "bytes " + mediaUploader.offset + "-" + (end - 1) + "/" + mediaUploader.file.size,
  3347. "X-Upload-Content-Type": mediaUploader.contentType
  3348. },
  3349. body: content
  3350. });
  3351. if (mediaUploader.onProgress && !mediaUploader.cancelled) {
  3352. mediaUploader.onProgress(mediaUploader.offset + mediaUploader.chunkSize, mediaUploader.file.size);
  3353. }
  3354. if (httpResponse.status == 200 || httpResponse.status == 201) {
  3355. return httpResponse.json();
  3356. } else if (httpResponse.status == 308) {
  3357. const range = httpResponse.headers.get("Range");
  3358. if (range) {
  3359. mediaUploader.offset = parseInt(range.match(/\d+/g).pop(), 10) + 1;
  3360. }
  3361. if (mediaUploader.cancelled) {
  3362. throw new Error("upload_cancelled");
  3363. } else {
  3364. return sendFile$1(mediaUploader);
  3365. }
  3366. } else {
  3367. getResponse$1(httpResponse);
  3368. }
  3369. }
  3370. async function getJSON$1(httpResponse) {
  3371. httpResponse = getResponse$1(httpResponse);
  3372. const response = await httpResponse.json();
  3373. if (response.error) {
  3374. throw new Error(response.error);
  3375. } else {
  3376. return response;
  3377. }
  3378. }
  3379. function getResponse$1(httpResponse) {
  3380. if (httpResponse.status == 200) {
  3381. return httpResponse;
  3382. } else if (httpResponse.status == 404) {
  3383. throw new Error("path_not_found");
  3384. } else if (httpResponse.status == 401) {
  3385. throw new Error("invalid_token");
  3386. } else {
  3387. throw new Error("unknown_error (" + httpResponse.status + ")");
  3388. }
  3389. }
  3390. /*
  3391. * Copyright 2010-2020 Gildas Lormeau
  3392. * contact : gildas.lormeau <at> gmail.com
  3393. *
  3394. * This file is part of SingleFile.
  3395. *
  3396. * The code in this file is free software: you can redistribute it and/or
  3397. * modify it under the terms of the GNU Affero General Public License
  3398. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  3399. * of the License, or (at your option) any later version.
  3400. *
  3401. * The code in this file is distributed in the hope that it will be useful,
  3402. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  3403. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  3404. * General Public License for more details.
  3405. *
  3406. * As additional permission under GNU AGPL version 3 section 7, you may
  3407. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  3408. * AGPL normally required by section 4, provided you include this license
  3409. * notice and a URL through which recipients can access the Corresponding
  3410. * Source.
  3411. */
  3412. /* global browser, fetch */
  3413. const TOKEN_URL = "https://api.dropboxapi.com/oauth2/token";
  3414. const AUTH_URL = "https://www.dropbox.com/oauth2/authorize";
  3415. const REVOKE_ACCESS_URL = "https://api.dropboxapi.com/2/auth/token/revoke";
  3416. const DROPBOX_SEARCH_URL = "https://api.dropboxapi.com/2/files/search_v2";
  3417. const DROPBOX_UPLOAD_URL = "https://content.dropboxapi.com/2/files/upload_session/start";
  3418. const DROPBOX_APPEND_URL = "https://content.dropboxapi.com/2/files/upload_session/append_v2";
  3419. const DROPBOX_FINISH_URL = "https://content.dropboxapi.com/2/files/upload_session/finish";
  3420. const CONFLICT_ACTION_UNIQUIFY$3 = "uniquify";
  3421. const CONFLICT_ACTION_OVERWRITE$2 = "overwrite";
  3422. const CONFLICT_ACTION_SKIP$3 = "skip";
  3423. const CONFLICT_ACTION_PROMPT$2 = "prompt";
  3424. const ENCODED_CHARS = /[\u007f-\uffff]/g;
  3425. class Dropbox {
  3426. constructor(clientId, clientKey) {
  3427. this.clientId = clientId;
  3428. this.clientKey = clientKey;
  3429. }
  3430. async auth(options = { interactive: true }) {
  3431. this.authURL = AUTH_URL +
  3432. "?client_id=" + this.clientId +
  3433. "&response_type=code" +
  3434. "&token_access_type=offline" +
  3435. "&redirect_uri=" + browser.identity.getRedirectURL();
  3436. return options.code ? authFromCode(this, options) : initAuth(this, options);
  3437. }
  3438. setAuthInfo(authInfo) {
  3439. if (authInfo) {
  3440. this.accessToken = authInfo.accessToken;
  3441. this.refreshToken = authInfo.refreshToken;
  3442. this.expirationDate = authInfo.expirationDate;
  3443. } else {
  3444. delete this.accessToken;
  3445. delete this.refreshToken;
  3446. delete this.expirationDate;
  3447. }
  3448. }
  3449. async refreshAuthToken() {
  3450. if (this.refreshToken) {
  3451. const httpResponse = await fetch(TOKEN_URL, {
  3452. method: "POST",
  3453. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  3454. body: "client_id=" + this.clientId +
  3455. "&refresh_token=" + this.refreshToken +
  3456. "&grant_type=refresh_token" +
  3457. "&client_secret=" + this.clientKey
  3458. });
  3459. if (httpResponse.status == 400) {
  3460. throw new Error("unknown_token");
  3461. }
  3462. const response = await getJSON(httpResponse);
  3463. this.accessToken = response.access_token;
  3464. if (response.refresh_token) {
  3465. this.refreshToken = response.refresh_token;
  3466. }
  3467. if (response.expires_in) {
  3468. this.expirationDate = Date.now() + (response.expires_in * 1000);
  3469. }
  3470. return { accessToken: this.accessToken, refreshToken: this.refreshToken, expirationDate: this.expirationDate };
  3471. } else {
  3472. delete this.accessToken;
  3473. }
  3474. }
  3475. async revokeAuthToken(accessToken) {
  3476. if (accessToken) {
  3477. const httpResponse = await fetch(REVOKE_ACCESS_URL, {
  3478. method: "POST",
  3479. headers: {
  3480. "Authorization": "Bearer " + accessToken
  3481. }
  3482. });
  3483. try {
  3484. await httpResponse.text();
  3485. }
  3486. catch (error) {
  3487. if (error.message != "invalid_token") {
  3488. throw error;
  3489. }
  3490. }
  3491. finally {
  3492. delete this.accessToken;
  3493. delete this.refreshToken;
  3494. delete this.expirationDate;
  3495. }
  3496. }
  3497. }
  3498. async upload(filename, blob, options, setCancelCallback) {
  3499. const uploader = new MediaUploader({
  3500. token: this.accessToken,
  3501. file: blob,
  3502. filename,
  3503. onProgress: options.onProgress,
  3504. filenameConflictAction: options.filenameConflictAction,
  3505. prompt: options.prompt
  3506. });
  3507. if (setCancelCallback) {
  3508. setCancelCallback(() => uploader.cancelled = true);
  3509. }
  3510. await uploader.upload();
  3511. }
  3512. }
  3513. class MediaUploader {
  3514. constructor(options) {
  3515. this.file = options.file;
  3516. this.onProgress = options.onProgress;
  3517. this.contentType = this.file.type || "application/octet-stream";
  3518. this.metadata = {
  3519. name: options.filename,
  3520. mimeType: this.contentType
  3521. };
  3522. this.token = options.token;
  3523. this.offset = 0;
  3524. this.chunkSize = options.chunkSize || 8 * 1024 * 1024;
  3525. this.filenameConflictAction = options.filenameConflictAction;
  3526. this.prompt = options.prompt;
  3527. }
  3528. async upload() {
  3529. const httpListResponse = getResponse(await fetch(DROPBOX_SEARCH_URL, {
  3530. method: "POST",
  3531. headers: {
  3532. "Authorization": "Bearer " + this.token,
  3533. "Content-Type": "application/json"
  3534. },
  3535. body: stringify({
  3536. query: this.metadata.name,
  3537. options: {
  3538. filename: true
  3539. }
  3540. })
  3541. }));
  3542. const response = await getJSON(httpListResponse);
  3543. if (response.matches.length) {
  3544. if (this.filenameConflictAction == CONFLICT_ACTION_PROMPT$2) {
  3545. if (this.prompt) {
  3546. const name = await this.prompt(this.metadata.name);
  3547. if (name) {
  3548. this.metadata.name = name;
  3549. } else {
  3550. return response;
  3551. }
  3552. } else {
  3553. this.filenameConflictAction = CONFLICT_ACTION_UNIQUIFY$3;
  3554. }
  3555. } else if (this.filenameConflictAction == CONFLICT_ACTION_SKIP$3) {
  3556. return response;
  3557. }
  3558. }
  3559. const httpResponse = getResponse(await fetch(DROPBOX_UPLOAD_URL, {
  3560. method: "POST",
  3561. headers: {
  3562. "Authorization": "Bearer " + this.token,
  3563. "Dropbox-API-Arg": stringify({
  3564. close: false
  3565. }),
  3566. "Content-Type": "application/octet-stream"
  3567. }
  3568. }));
  3569. const sessionId = (await getJSON(httpResponse)).session_id;
  3570. this.sessionId = sessionId;
  3571. if (!this.cancelled) {
  3572. if (this.onProgress) {
  3573. this.onProgress(0, this.file.size);
  3574. }
  3575. return sendFile(this);
  3576. }
  3577. }
  3578. }
  3579. async function authFromCode(dropbox, options) {
  3580. const httpResponse = await fetch(TOKEN_URL, {
  3581. method: "POST",
  3582. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  3583. body: "client_id=" + dropbox.clientId +
  3584. "&client_secret=" + dropbox.clientKey +
  3585. "&grant_type=authorization_code" +
  3586. "&code=" + options.code +
  3587. "&redirect_uri=" + browser.identity.getRedirectURL()
  3588. });
  3589. const response = await getJSON(httpResponse);
  3590. dropbox.accessToken = response.access_token;
  3591. dropbox.refreshToken = response.refresh_token;
  3592. dropbox.expirationDate = Date.now() + (response.expires_in * 1000);
  3593. return { accessToken: dropbox.accessToken, refreshToken: dropbox.refreshToken, expirationDate: dropbox.expirationDate };
  3594. }
  3595. async function initAuth(dropbox, options) {
  3596. let code;
  3597. try {
  3598. options.extractAuthCode(browser.identity.getRedirectURL())
  3599. .then(authCode => code = authCode)
  3600. .catch(() => { /* ignored */ });
  3601. return await options.launchWebAuthFlow({ url: dropbox.authURL });
  3602. }
  3603. catch (error) {
  3604. if (error.message && (error.message == "code_required" || error.message.includes("access"))) {
  3605. if (code) {
  3606. options.code = code;
  3607. return await authFromCode(dropbox, options);
  3608. } else {
  3609. throw new Error("code_required");
  3610. }
  3611. } else {
  3612. throw error;
  3613. }
  3614. }
  3615. }
  3616. async function sendFile(mediaUploader) {
  3617. let content = mediaUploader.file, end = mediaUploader.file.size;
  3618. if (mediaUploader.offset || mediaUploader.chunkSize) {
  3619. if (mediaUploader.chunkSize) {
  3620. end = Math.min(mediaUploader.offset + mediaUploader.chunkSize, mediaUploader.file.size);
  3621. }
  3622. content = content.slice(mediaUploader.offset, end);
  3623. }
  3624. const httpAppendResponse = getResponse(await fetch(DROPBOX_APPEND_URL, {
  3625. method: "POST",
  3626. headers: {
  3627. "Authorization": "Bearer " + mediaUploader.token,
  3628. "Content-Type": "application/octet-stream",
  3629. "Dropbox-API-Arg": stringify({
  3630. cursor: {
  3631. session_id: mediaUploader.sessionId,
  3632. offset: mediaUploader.offset
  3633. },
  3634. close: end == mediaUploader.file.size
  3635. })
  3636. },
  3637. body: content
  3638. }));
  3639. if (mediaUploader.onProgress && !mediaUploader.cancelled) {
  3640. mediaUploader.onProgress(mediaUploader.offset + mediaUploader.chunkSize, mediaUploader.file.size);
  3641. }
  3642. if (httpAppendResponse.status == 200) {
  3643. mediaUploader.offset = end;
  3644. if (mediaUploader.offset < mediaUploader.file.size) {
  3645. return sendFile(mediaUploader);
  3646. }
  3647. }
  3648. let path = mediaUploader.metadata.name;
  3649. if (!path.startsWith("/")) {
  3650. path = "/" + path;
  3651. }
  3652. const httpFinishResponse = await fetch(DROPBOX_FINISH_URL, {
  3653. method: "POST",
  3654. headers: {
  3655. "Authorization": "Bearer " + mediaUploader.token,
  3656. "Content-Type": "application/octet-stream",
  3657. "Dropbox-API-Arg": stringify({
  3658. cursor: {
  3659. session_id: mediaUploader.sessionId,
  3660. offset: mediaUploader.offset
  3661. },
  3662. commit: {
  3663. path,
  3664. mode: mediaUploader.filenameConflictAction == CONFLICT_ACTION_OVERWRITE$2 ? "overwrite" : "add",
  3665. autorename: mediaUploader.filenameConflictAction == CONFLICT_ACTION_UNIQUIFY$3
  3666. }
  3667. })
  3668. }
  3669. });
  3670. if (httpFinishResponse.status == 200) {
  3671. return getJSON(httpFinishResponse);
  3672. } else if (httpFinishResponse.status == 409 && mediaUploader.filenameConflictAction == CONFLICT_ACTION_PROMPT$2) {
  3673. mediaUploader.offset = 0;
  3674. return mediaUploader.upload();
  3675. } else {
  3676. throw new Error("unknown_error (" + httpFinishResponse.status + ")");
  3677. }
  3678. }
  3679. async function getJSON(httpResponse) {
  3680. httpResponse = getResponse(httpResponse);
  3681. const response = await httpResponse.json();
  3682. if (response.error) {
  3683. throw new Error(response.error);
  3684. } else {
  3685. return response;
  3686. }
  3687. }
  3688. function getResponse(httpResponse) {
  3689. if (httpResponse.status == 200) {
  3690. return httpResponse;
  3691. } else if (httpResponse.status == 401) {
  3692. throw new Error("invalid_token");
  3693. } else {
  3694. throw new Error("unknown_error (" + httpResponse.status + ")");
  3695. }
  3696. }
  3697. function stringify(value) {
  3698. return JSON.stringify(value).replace(ENCODED_CHARS,
  3699. function (c) {
  3700. return "\\u" + ("000" + c.charCodeAt(0).toString(16)).slice(-4);
  3701. }
  3702. );
  3703. }
  3704. /*
  3705. * Copyright 2010-2020 Gildas Lormeau
  3706. * contact : gildas.lormeau <at> gmail.com
  3707. *
  3708. * This file is part of SingleFile.
  3709. *
  3710. * The code in this file is free software: you can redistribute it and/or
  3711. * modify it under the terms of the GNU Affero General Public License
  3712. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  3713. * of the License, or (at your option) any later version.
  3714. *
  3715. * The code in this file is distributed in the hope that it will be useful,
  3716. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  3717. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  3718. * General Public License for more details.
  3719. *
  3720. * As additional permission under GNU AGPL version 3 section 7, you may
  3721. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  3722. * AGPL normally required by section 4, provided you include this license
  3723. * notice and a URL through which recipients can access the Corresponding
  3724. * Source.
  3725. */
  3726. /* global fetch, btoa, AbortController */
  3727. const EMPTY_STRING$1 = "";
  3728. const CONFLICT_ACTION_SKIP$2 = "skip";
  3729. const CONFLICT_ACTION_UNIQUIFY$2 = "uniquify";
  3730. const CONFLICT_ACTION_OVERWRITE$1 = "overwrite";
  3731. const CONFLICT_ACTION_PROMPT$1 = "prompt";
  3732. const BASIC_PREFIX_AUTHORIZATION = "Basic ";
  3733. const AUTHORIZATION_HEADER$2 = "Authorization";
  3734. const AUTHORIZATION_SEPARATOR = ":";
  3735. const DIRECTORY_SEPARATOR = "/";
  3736. const EXTENSION_SEPARATOR$1 = ".";
  3737. const ERROR_PREFIX_MESSAGE = "Error ";
  3738. const INDEX_FILENAME_PREFIX$1 = " (";
  3739. const INDEX_FILENAME_SUFFIX$1 = ")";
  3740. const INDEX_FILENAME_REGEXP$1 = /\s\((\d+)\)$/;
  3741. const ABORT_ERROR_NAME$1 = "AbortError";
  3742. const HEAD_METHOD = "HEAD";
  3743. const PUT_METHOD$1 = "PUT";
  3744. const DELETE_METHOD = "DELETE";
  3745. const PROPFIND_METHOD = "PROPFIND";
  3746. const MKCOL_METHOD = "MKCOL";
  3747. const CONTENT_TYPE_HEADER = "Content-Type";
  3748. const HTML_CONTENT_TYPE = "text/html";
  3749. const CREDENTIALS_PARAMETER = "omit";
  3750. const FOUND_STATUS = 200;
  3751. const CREATED_STATUS = 201;
  3752. const NOT_FOUND_STATUS = 404;
  3753. const MIN_ERROR_STATUS = 400;
  3754. class WebDAV {
  3755. constructor(url, username, password) {
  3756. if (!url.endsWith(DIRECTORY_SEPARATOR)) {
  3757. url += DIRECTORY_SEPARATOR;
  3758. }
  3759. this.url = url;
  3760. this.authorization = BASIC_PREFIX_AUTHORIZATION + btoa(username + AUTHORIZATION_SEPARATOR + password);
  3761. }
  3762. upload(filename, content, options) {
  3763. this.controller = new AbortController();
  3764. options.signal = this.controller.signal;
  3765. options.authorization = this.authorization;
  3766. options.url = this.url;
  3767. return upload$1(filename, content, options);
  3768. }
  3769. abort() {
  3770. if (this.controller) {
  3771. this.controller.abort();
  3772. }
  3773. }
  3774. }
  3775. async function upload$1(filename, content, options) {
  3776. const { authorization, filenameConflictAction, prompt, signal, preventRetry } = options;
  3777. let { url } = options;
  3778. try {
  3779. if (filenameConflictAction == CONFLICT_ACTION_OVERWRITE$1) {
  3780. let response = await sendRequest(filename, PUT_METHOD$1, content);
  3781. if (response.status == CREATED_STATUS) {
  3782. return response;
  3783. } else if (response.status >= MIN_ERROR_STATUS) {
  3784. response = await sendRequest(filename, DELETE_METHOD);
  3785. if (response.status >= MIN_ERROR_STATUS) {
  3786. throw new Error(ERROR_PREFIX_MESSAGE + response.status);
  3787. }
  3788. return await upload$1(filename, content, options);
  3789. }
  3790. } else {
  3791. let response = await sendRequest(filename, HEAD_METHOD);
  3792. if (response.status == FOUND_STATUS) {
  3793. if (filenameConflictAction == CONFLICT_ACTION_UNIQUIFY$2 || (filenameConflictAction == CONFLICT_ACTION_PROMPT$1 && !prompt)) {
  3794. const { filenameWithoutExtension, extension, indexFilename } = splitFilename(filename);
  3795. options.indexFilename = indexFilename + 1;
  3796. return await upload$1(getFilename(filenameWithoutExtension, extension), content, options);
  3797. } else if (filenameConflictAction == CONFLICT_ACTION_PROMPT$1) {
  3798. filename = await prompt(filename);
  3799. return filename ? upload$1(filename, content, options) : response;
  3800. } else if (filenameConflictAction == CONFLICT_ACTION_SKIP$2) {
  3801. return response;
  3802. }
  3803. } else if (response.status == NOT_FOUND_STATUS) {
  3804. response = await sendRequest(filename, PUT_METHOD$1, content);
  3805. if (response.status >= MIN_ERROR_STATUS && !preventRetry) {
  3806. if (filename.includes(DIRECTORY_SEPARATOR)) {
  3807. await createDirectories();
  3808. options.preventRetry = true;
  3809. return await upload$1(filename, content, options);
  3810. } else {
  3811. throw new Error(ERROR_PREFIX_MESSAGE + response.status);
  3812. }
  3813. } else {
  3814. return response;
  3815. }
  3816. } else if (response.status >= MIN_ERROR_STATUS) {
  3817. throw new Error(ERROR_PREFIX_MESSAGE + response.status);
  3818. }
  3819. }
  3820. } catch (error) {
  3821. if (error.name != ABORT_ERROR_NAME$1) {
  3822. throw error;
  3823. }
  3824. }
  3825. function sendRequest(path, method, body) {
  3826. const headers = {
  3827. [AUTHORIZATION_HEADER$2]: authorization
  3828. };
  3829. if (body) {
  3830. headers[CONTENT_TYPE_HEADER] = HTML_CONTENT_TYPE;
  3831. }
  3832. return fetch(url + path, { method, headers, signal, body, credentials: CREDENTIALS_PARAMETER });
  3833. }
  3834. function splitFilename(filename) {
  3835. let filenameWithoutExtension = filename;
  3836. let extension = EMPTY_STRING$1;
  3837. const indexExtensionSeparator = filename.lastIndexOf(EXTENSION_SEPARATOR$1);
  3838. if (indexExtensionSeparator > -1) {
  3839. filenameWithoutExtension = filename.substring(0, indexExtensionSeparator);
  3840. extension = filename.substring(indexExtensionSeparator + 1);
  3841. }
  3842. let indexFilename;
  3843. ({ filenameWithoutExtension, indexFilename } = extractIndexFilename(filenameWithoutExtension));
  3844. return { filenameWithoutExtension, extension, indexFilename };
  3845. }
  3846. function extractIndexFilename(filenameWithoutExtension) {
  3847. const indexFilenameMatch = filenameWithoutExtension.match(INDEX_FILENAME_REGEXP$1);
  3848. let indexFilename = 0;
  3849. if (indexFilenameMatch && indexFilenameMatch.length > 1) {
  3850. const parsedIndexFilename = Number(indexFilenameMatch[indexFilenameMatch.length - 1]);
  3851. if (!Number.isNaN(parsedIndexFilename)) {
  3852. indexFilename = parsedIndexFilename;
  3853. filenameWithoutExtension = filenameWithoutExtension.replace(INDEX_FILENAME_REGEXP$1, EMPTY_STRING$1);
  3854. }
  3855. }
  3856. return { filenameWithoutExtension, indexFilename };
  3857. }
  3858. function getFilename(filenameWithoutExtension, extension) {
  3859. return filenameWithoutExtension +
  3860. INDEX_FILENAME_PREFIX$1 + options.indexFilename + INDEX_FILENAME_SUFFIX$1 +
  3861. (extension ? EXTENSION_SEPARATOR$1 + extension : EMPTY_STRING$1);
  3862. }
  3863. async function createDirectories() {
  3864. const filenameParts = filename.split(DIRECTORY_SEPARATOR);
  3865. filenameParts.pop();
  3866. let path = EMPTY_STRING$1;
  3867. for (const filenamePart of filenameParts) {
  3868. if (filenamePart) {
  3869. path += filenamePart;
  3870. const response = await sendRequest(path, PROPFIND_METHOD);
  3871. if (response.status == NOT_FOUND_STATUS) {
  3872. const response = await sendRequest(path, MKCOL_METHOD);
  3873. if (response.status >= MIN_ERROR_STATUS) {
  3874. throw new Error(ERROR_PREFIX_MESSAGE + response.status);
  3875. }
  3876. }
  3877. path += DIRECTORY_SEPARATOR;
  3878. }
  3879. }
  3880. }
  3881. }
  3882. /*
  3883. * Copyright 2010-2020 Gildas Lormeau
  3884. * contact : gildas.lormeau <at> gmail.com
  3885. *
  3886. * This file is part of SingleFile.
  3887. *
  3888. * The code in this file is free software: you can redistribute it and/or
  3889. * modify it under the terms of the GNU Affero General Public License
  3890. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  3891. * of the License, or (at your option) any later version.
  3892. *
  3893. * The code in this file is distributed in the hope that it will be useful,
  3894. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  3895. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  3896. * General Public License for more details.
  3897. *
  3898. * As additional permission under GNU AGPL version 3 section 7, you may
  3899. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  3900. * AGPL normally required by section 4, provided you include this license
  3901. * notice and a URL through which recipients can access the Corresponding
  3902. * Source.
  3903. */
  3904. /* global fetch, btoa, Blob, FileReader, AbortController */
  3905. const EMPTY_STRING = "";
  3906. const CONFLICT_ACTION_SKIP$1 = "skip";
  3907. const CONFLICT_ACTION_UNIQUIFY$1 = "uniquify";
  3908. const CONFLICT_ACTION_OVERWRITE = "overwrite";
  3909. const CONFLICT_ACTION_PROMPT = "prompt";
  3910. const AUTHORIZATION_HEADER$1 = "Authorization";
  3911. const BEARER_PREFIX_AUTHORIZATION$1 = "Bearer ";
  3912. const ACCEPT_HEADER$1 = "Accept";
  3913. const GITHUB_API_CONTENT_TYPE = "application/vnd.github+json";
  3914. const GITHUB_API_VERSION_HEADER = "X-GitHub-Api-Version";
  3915. const GITHUB_API_VERSION = "2022-11-28";
  3916. const EXTENSION_SEPARATOR = ".";
  3917. const INDEX_FILENAME_PREFIX = " (";
  3918. const INDEX_FILENAME_SUFFIX = ")";
  3919. const INDEX_FILENAME_REGEXP = /\s\((\d+)\)$/;
  3920. const ABORT_ERROR_NAME = "AbortError";
  3921. const GET_METHOD = "GET";
  3922. const PUT_METHOD = "PUT";
  3923. const GITHUB_URL = "https://github.com";
  3924. const GITHUB_API_URL = "https://api.github.com";
  3925. const BLOB_PATH = "blob";
  3926. const REPOS_PATH = "repos";
  3927. const CONTENTS_PATH = "contents";
  3928. let pendingPush;
  3929. class GitHub {
  3930. constructor(token, userName, repositoryName, branch) {
  3931. this.headers = new Map([
  3932. [AUTHORIZATION_HEADER$1, BEARER_PREFIX_AUTHORIZATION$1 + token],
  3933. [ACCEPT_HEADER$1, GITHUB_API_CONTENT_TYPE],
  3934. [GITHUB_API_VERSION_HEADER, GITHUB_API_VERSION]
  3935. ]);
  3936. this.userName = userName;
  3937. this.repositoryName = repositoryName;
  3938. this.branch = branch;
  3939. }
  3940. async upload(path, content, options) {
  3941. this.controller = new AbortController();
  3942. options.signal = this.controller.signal;
  3943. options.headers = this.headers;
  3944. const base64Content = content instanceof Blob ? await blobToBase64(content) : btoa(unescape(encodeURIComponent(content)));
  3945. return upload(this.userName, this.repositoryName, this.branch, path, base64Content, options);
  3946. }
  3947. abort() {
  3948. if (this.controller) {
  3949. this.controller.abort();
  3950. }
  3951. }
  3952. }
  3953. async function upload(userName, repositoryName, branch, path, content, options) {
  3954. const { filenameConflictAction, prompt, signal, headers } = options;
  3955. while (pendingPush) {
  3956. await pendingPush;
  3957. }
  3958. try {
  3959. pendingPush = await createContent({ path, content });
  3960. } finally {
  3961. pendingPush = null;
  3962. }
  3963. return {
  3964. url: `${GITHUB_URL}/${userName}/${repositoryName}/${BLOB_PATH}/${branch}/${path}`
  3965. };
  3966. async function createContent({ path, content, message = EMPTY_STRING, sha }) {
  3967. try {
  3968. const response = await fetchContentData(PUT_METHOD, JSON.stringify({
  3969. content,
  3970. message,
  3971. branch,
  3972. sha
  3973. }));
  3974. const responseData = await response.json();
  3975. if (response.status == 422) {
  3976. if (filenameConflictAction == CONFLICT_ACTION_OVERWRITE) {
  3977. const response = await fetchContentData(GET_METHOD);
  3978. const responseData = await response.json();
  3979. const sha = responseData.sha;
  3980. return await createContent({ path, content, message, sha });
  3981. } else if (filenameConflictAction == CONFLICT_ACTION_UNIQUIFY$1) {
  3982. const { filenameWithoutExtension, extension, indexFilename } = splitFilename(path);
  3983. options.indexFilename = indexFilename + 1;
  3984. path = getFilename(filenameWithoutExtension, extension);
  3985. return await createContent({ path, content, message });
  3986. } else if (filenameConflictAction == CONFLICT_ACTION_SKIP$1) {
  3987. return responseData;
  3988. } else if (filenameConflictAction == CONFLICT_ACTION_PROMPT) {
  3989. if (prompt) {
  3990. path = await prompt(path);
  3991. if (path) {
  3992. return await createContent({ path, content, message });
  3993. } else {
  3994. return responseData;
  3995. }
  3996. } else {
  3997. options.filenameConflictAction = CONFLICT_ACTION_UNIQUIFY$1;
  3998. return await createContent({ path, content, message });
  3999. }
  4000. }
  4001. }
  4002. if (response.status < 400) {
  4003. return responseData;
  4004. } else {
  4005. throw new Error(responseData.message);
  4006. }
  4007. } catch (error) {
  4008. if (error.name != ABORT_ERROR_NAME) {
  4009. throw error;
  4010. }
  4011. }
  4012. function fetchContentData(method, body) {
  4013. return fetch(`${GITHUB_API_URL}/${REPOS_PATH}/${userName}/${repositoryName}/${CONTENTS_PATH}/${path}`, {
  4014. method,
  4015. headers,
  4016. body,
  4017. signal
  4018. });
  4019. }
  4020. }
  4021. function splitFilename(filename) {
  4022. let filenameWithoutExtension = filename;
  4023. let extension = EMPTY_STRING;
  4024. const indexExtensionSeparator = filename.lastIndexOf(EXTENSION_SEPARATOR);
  4025. if (indexExtensionSeparator > -1) {
  4026. filenameWithoutExtension = filename.substring(0, indexExtensionSeparator);
  4027. extension = filename.substring(indexExtensionSeparator + 1);
  4028. }
  4029. let indexFilename;
  4030. ({ filenameWithoutExtension, indexFilename } = extractIndexFilename(filenameWithoutExtension));
  4031. return { filenameWithoutExtension, extension, indexFilename };
  4032. }
  4033. function extractIndexFilename(filenameWithoutExtension) {
  4034. const indexFilenameMatch = filenameWithoutExtension.match(INDEX_FILENAME_REGEXP);
  4035. let indexFilename = 0;
  4036. if (indexFilenameMatch && indexFilenameMatch.length > 1) {
  4037. const parsedIndexFilename = Number(indexFilenameMatch[indexFilenameMatch.length - 1]);
  4038. if (!Number.isNaN(parsedIndexFilename)) {
  4039. indexFilename = parsedIndexFilename;
  4040. filenameWithoutExtension = filenameWithoutExtension.replace(INDEX_FILENAME_REGEXP, EMPTY_STRING);
  4041. }
  4042. }
  4043. return { filenameWithoutExtension, indexFilename };
  4044. }
  4045. function getFilename(filenameWithoutExtension, extension) {
  4046. return filenameWithoutExtension +
  4047. INDEX_FILENAME_PREFIX + options.indexFilename + INDEX_FILENAME_SUFFIX +
  4048. (extension ? EXTENSION_SEPARATOR + extension : EMPTY_STRING);
  4049. }
  4050. }
  4051. function blobToBase64(blob) {
  4052. return new Promise((resolve, reject) => {
  4053. const reader = new FileReader();
  4054. reader.onloadend = () => resolve(reader.result.match(/^data:[^,]+,(.*)$/)[1]);
  4055. reader.onerror = event => reject(event.detail);
  4056. reader.readAsDataURL(blob);
  4057. });
  4058. }
  4059. /* global TextEncoder, TextDecoder */
  4060. const DEFAULT_CHUNK_SIZE = 8 * 1024 * 1024;
  4061. const TYPE_REFERENCE = 0;
  4062. const SPECIAL_TYPES = [TYPE_REFERENCE];
  4063. const EMPTY_SLOT_VALUE = Symbol();
  4064. const textEncoder = new TextEncoder();
  4065. const textDecoder = new TextDecoder();
  4066. const types = new Array(256);
  4067. let typeIndex = 0;
  4068. registerType(serializeCircularReference, parseCircularReference, testCircularReference, TYPE_REFERENCE);
  4069. registerType(null, parseObject, testObject);
  4070. registerType(serializeArray, parseArray, testArray);
  4071. registerType(serializeString, parseString, testString);
  4072. registerType(serializeTypedArray, parseFloat64Array, testFloat64Array);
  4073. registerType(serializeTypedArray, parseFloat32Array, testFloat32Array);
  4074. registerType(serializeTypedArray, parseUint32Array, testUint32Array);
  4075. registerType(serializeTypedArray, parseInt32Array, testInt32Array);
  4076. registerType(serializeTypedArray, parseUint16Array, testUint16Array);
  4077. registerType(serializeTypedArray, parseInt16Array, testInt16Array);
  4078. registerType(serializeTypedArray, parseUint8ClampedArray, testUint8ClampedArray);
  4079. registerType(serializeTypedArray, parseUint8Array, testUint8Array);
  4080. registerType(serializeTypedArray, parseInt8Array, testInt8Array);
  4081. registerType(serializeArrayBuffer, parseArrayBuffer, testArrayBuffer);
  4082. registerType(serializeNumber, parseNumber, testNumber);
  4083. registerType(serializeUint32, parseUint32, testUint32);
  4084. registerType(serializeInt32, parseInt32, testInt32);
  4085. registerType(serializeUint16, parseUint16, testUint16);
  4086. registerType(serializeInt16, parseInt16, testInt16);
  4087. registerType(serializeUint8, parseUint8, testUint8);
  4088. registerType(serializeInt8, parseInt8, testInt8);
  4089. registerType(null, parseUndefined, testUndefined);
  4090. registerType(null, parseNull, testNull);
  4091. registerType(null, parseNaN, testNaN);
  4092. registerType(serializeBoolean, parseBoolean, testBoolean);
  4093. registerType(serializeSymbol, parseSymbol, testSymbol);
  4094. registerType(null, parseEmptySlot, testEmptySlot);
  4095. registerType(serializeMap, parseMap, testMap);
  4096. registerType(serializeSet, parseSet, testSet);
  4097. registerType(serializeDate, parseDate, testDate);
  4098. registerType(serializeError, parseError, testError);
  4099. registerType(serializeRegExp, parseRegExp, testRegExp);
  4100. registerType(serializeStringObject, parseStringObject, testStringObject);
  4101. registerType(serializeNumberObject, parseNumberObject, testNumberObject);
  4102. registerType(serializeBooleanObject, parseBooleanObject, testBooleanObject);
  4103. function registerType(serialize, parse, test, type) {
  4104. if (type === undefined) {
  4105. typeIndex++;
  4106. if (types.length - typeIndex >= SPECIAL_TYPES.length) {
  4107. types[types.length - typeIndex] = { serialize, parse, test };
  4108. } else {
  4109. throw new Error("Reached maximum number of custom types");
  4110. }
  4111. } else {
  4112. types[type] = { serialize, parse, test };
  4113. }
  4114. }
  4115. async function parse(array) {
  4116. const parser = getParser();
  4117. await parser.next(array);
  4118. const result = await parser.next();
  4119. return result.value;
  4120. }
  4121. class SerializerData {
  4122. constructor(appendData, chunkSize) {
  4123. this.stream = new WriteStream(appendData, chunkSize);
  4124. this.objects = [];
  4125. }
  4126. append(array) {
  4127. return this.stream.append(array);
  4128. }
  4129. flush() {
  4130. return this.stream.flush();
  4131. }
  4132. addObject(value) {
  4133. this.objects.push(testReferenceable(value) && !testCircularReference(value, this) ? value : undefined);
  4134. }
  4135. }
  4136. class WriteStream {
  4137. constructor(appendData, chunkSize) {
  4138. this.offset = 0;
  4139. this.appendData = appendData;
  4140. this.value = new Uint8Array(chunkSize);
  4141. }
  4142. async append(array) {
  4143. if (this.offset + array.length > this.value.length) {
  4144. const offset = this.value.length - this.offset;
  4145. await this.append(array.subarray(0, offset));
  4146. await this.appendData({ value: this.value });
  4147. this.offset = 0;
  4148. await this.append(array.subarray(offset));
  4149. } else {
  4150. this.value.set(array, this.offset);
  4151. this.offset += array.length;
  4152. }
  4153. }
  4154. async flush() {
  4155. if (this.offset) {
  4156. await this.appendData({ value: this.value.subarray(0, this.offset), done: true });
  4157. }
  4158. }
  4159. }
  4160. function getSerializer(value, { chunkSize = DEFAULT_CHUNK_SIZE } = {}) {
  4161. let serializerData, result, setResult, iterationDone, previousResult, resolvePreviousResult;
  4162. return {
  4163. [Symbol.asyncIterator]() {
  4164. return {
  4165. next() {
  4166. return iterationDone ? { done: iterationDone } : getResult();
  4167. },
  4168. return() {
  4169. return { done: true };
  4170. }
  4171. };
  4172. }
  4173. };
  4174. async function getResult() {
  4175. if (resolvePreviousResult) {
  4176. resolvePreviousResult();
  4177. } else {
  4178. initSerializerData().catch(() => { /* ignored */ });
  4179. }
  4180. initPreviousData();
  4181. const value = await getValue();
  4182. return { value };
  4183. }
  4184. async function initSerializerData() {
  4185. initResult();
  4186. serializerData = new SerializerData(appendData, chunkSize);
  4187. await serializeValue(serializerData, value);
  4188. await serializerData.flush();
  4189. }
  4190. function initResult() {
  4191. result = new Promise(resolve => setResult = resolve);
  4192. }
  4193. function initPreviousData() {
  4194. previousResult = new Promise(resolve => resolvePreviousResult = resolve);
  4195. }
  4196. async function appendData(result) {
  4197. setResult(result);
  4198. await previousResult;
  4199. }
  4200. async function getValue() {
  4201. const { value, done } = await result;
  4202. iterationDone = done;
  4203. if (!done) {
  4204. initResult();
  4205. }
  4206. return value;
  4207. }
  4208. }
  4209. async function serializeValue(data, value) {
  4210. const type = types.findIndex(({ test } = {}) => test && test(value, data));
  4211. data.addObject(value);
  4212. await data.append(new Uint8Array([type]));
  4213. const serialize = types[type].serialize;
  4214. if (serialize) {
  4215. await serialize(data, value);
  4216. }
  4217. if (type != TYPE_REFERENCE && testObject(value)) {
  4218. await serializeSymbols(data, value);
  4219. await serializeOwnProperties(data, value);
  4220. }
  4221. }
  4222. async function serializeSymbols(data, value) {
  4223. const ownPropertySymbols = Object.getOwnPropertySymbols(value);
  4224. const symbols = ownPropertySymbols.map(propertySymbol => [propertySymbol, value[propertySymbol]]);
  4225. await serializeArray(data, symbols);
  4226. }
  4227. async function serializeOwnProperties(data, value) {
  4228. if (!ArrayBuffer.isView(value)) {
  4229. let entries = Object.entries(value);
  4230. if (testArray(value)) {
  4231. entries = entries.filter(([key]) => !testInteger(Number(key)));
  4232. }
  4233. await serializeValue(data, entries.length);
  4234. for (const [key, value] of entries) {
  4235. await serializeString(data, key);
  4236. await serializeValue(data, value);
  4237. }
  4238. } else {
  4239. await serializeValue(data, 0);
  4240. }
  4241. }
  4242. async function serializeCircularReference(data, value) {
  4243. const index = data.objects.indexOf(value);
  4244. await serializeValue(data, index);
  4245. }
  4246. async function serializeArray(data, array) {
  4247. await serializeValue(data, array.length);
  4248. const notEmptyIndexes = Object.keys(array).filter(key => testInteger(Number(key))).map(key => Number(key));
  4249. let indexNotEmptyIndexes = 0, currentNotEmptyIndex = notEmptyIndexes[indexNotEmptyIndexes];
  4250. for (const [indexArray, value] of array.entries()) {
  4251. if (currentNotEmptyIndex == indexArray) {
  4252. currentNotEmptyIndex = notEmptyIndexes[++indexNotEmptyIndexes];
  4253. await serializeValue(data, value);
  4254. } else {
  4255. await serializeValue(data, EMPTY_SLOT_VALUE);
  4256. }
  4257. }
  4258. }
  4259. async function serializeString(data, string) {
  4260. const encodedString = textEncoder.encode(string);
  4261. await serializeValue(data, encodedString.length);
  4262. await data.append(encodedString);
  4263. }
  4264. async function serializeTypedArray(data, array) {
  4265. await serializeValue(data, array.length);
  4266. await data.append(array.constructor.name == "Uint8Array" ? array : new Uint8Array(array.buffer));
  4267. }
  4268. async function serializeArrayBuffer(data, arrayBuffer) {
  4269. await serializeValue(data, arrayBuffer.byteLength);
  4270. await data.append(new Uint8Array(arrayBuffer));
  4271. }
  4272. async function serializeNumber(data, number) {
  4273. const serializedNumber = new Uint8Array(new Float64Array([number]).buffer);
  4274. await data.append(serializedNumber);
  4275. }
  4276. async function serializeUint32(data, number) {
  4277. const serializedNumber = new Uint8Array(new Uint32Array([number]).buffer);
  4278. await data.append(serializedNumber);
  4279. }
  4280. async function serializeInt32(data, number) {
  4281. const serializedNumber = new Uint8Array(new Int32Array([number]).buffer);
  4282. await data.append(serializedNumber);
  4283. }
  4284. async function serializeUint16(data, number) {
  4285. const serializedNumber = new Uint8Array(new Uint16Array([number]).buffer);
  4286. await data.append(serializedNumber);
  4287. }
  4288. async function serializeInt16(data, number) {
  4289. const serializedNumber = new Uint8Array(new Int16Array([number]).buffer);
  4290. await data.append(serializedNumber);
  4291. }
  4292. async function serializeUint8(data, number) {
  4293. const serializedNumber = new Uint8Array([number]);
  4294. await data.append(serializedNumber);
  4295. }
  4296. async function serializeInt8(data, number) {
  4297. const serializedNumber = new Uint8Array(new Int8Array([number]).buffer);
  4298. await data.append(serializedNumber);
  4299. }
  4300. async function serializeBoolean(data, boolean) {
  4301. const serializedBoolean = new Uint8Array([Number(boolean)]);
  4302. await data.append(serializedBoolean);
  4303. }
  4304. async function serializeMap(data, map) {
  4305. const entries = map.entries();
  4306. await serializeValue(data, map.size);
  4307. for (const [key, value] of entries) {
  4308. await serializeValue(data, key);
  4309. await serializeValue(data, value);
  4310. }
  4311. }
  4312. async function serializeSet(data, set) {
  4313. await serializeValue(data, set.size);
  4314. for (const value of set) {
  4315. await serializeValue(data, value);
  4316. }
  4317. }
  4318. async function serializeDate(data, date) {
  4319. await serializeNumber(data, date.getTime());
  4320. }
  4321. async function serializeError(data, error) {
  4322. await serializeString(data, error.message);
  4323. await serializeString(data, error.stack);
  4324. }
  4325. async function serializeRegExp(data, regExp) {
  4326. await serializeString(data, regExp.source);
  4327. await serializeString(data, regExp.flags);
  4328. }
  4329. async function serializeStringObject(data, string) {
  4330. await serializeString(data, string.valueOf());
  4331. }
  4332. async function serializeNumberObject(data, number) {
  4333. await serializeNumber(data, number.valueOf());
  4334. }
  4335. async function serializeBooleanObject(data, boolean) {
  4336. await serializeBoolean(data, boolean.valueOf());
  4337. }
  4338. async function serializeSymbol(data, symbol) {
  4339. await serializeString(data, symbol.description);
  4340. }
  4341. class Reference {
  4342. constructor(index, data) {
  4343. this.index = index;
  4344. this.data = data;
  4345. }
  4346. getObject() {
  4347. return this.data.objects[this.index];
  4348. }
  4349. }
  4350. class ParserData {
  4351. constructor(consumeData) {
  4352. this.stream = new ReadStream(consumeData);
  4353. this.objects = [];
  4354. this.setters = [];
  4355. }
  4356. consume(size) {
  4357. return this.stream.consume(size);
  4358. }
  4359. getObjectId() {
  4360. const objectIndex = this.objects.length;
  4361. this.objects.push(undefined);
  4362. return objectIndex;
  4363. }
  4364. resolveObject(objectId, value) {
  4365. if (testReferenceable(value) && !testReference(value)) {
  4366. this.objects[objectId] = value;
  4367. }
  4368. }
  4369. setObject(functionArguments, setterFunction) {
  4370. this.setters.push({ functionArguments, setterFunction });
  4371. }
  4372. executeSetters() {
  4373. this.setters.forEach(({ functionArguments, setterFunction }) => {
  4374. const resolvedArguments = functionArguments.map(argument => testReference(argument) ? argument.getObject() : argument);
  4375. setterFunction(...resolvedArguments);
  4376. });
  4377. }
  4378. }
  4379. class ReadStream {
  4380. constructor(consumeData) {
  4381. this.offset = 0;
  4382. this.value = new Uint8Array(0);
  4383. this.consumeData = consumeData;
  4384. }
  4385. async consume(size) {
  4386. if (this.offset + size > this.value.length) {
  4387. const pending = this.value.subarray(this.offset, this.value.length);
  4388. const value = await this.consumeData();
  4389. if (pending.length + value.length != this.value.length) {
  4390. this.value = new Uint8Array(pending.length + value.length);
  4391. }
  4392. this.value.set(pending);
  4393. this.value.set(value, pending.length);
  4394. this.offset = 0;
  4395. return this.consume(size);
  4396. } else {
  4397. const result = this.value.slice(this.offset, this.offset + size);
  4398. this.offset += result.length;
  4399. return result;
  4400. }
  4401. }
  4402. }
  4403. function getParser() {
  4404. let parserData, input, setInput, value, previousData, resolvePreviousData;
  4405. return {
  4406. async next(input) {
  4407. return input ? getResult(input) : { value: await value, done: true };
  4408. },
  4409. return() {
  4410. return { done: true };
  4411. }
  4412. };
  4413. async function getResult(input) {
  4414. if (previousData) {
  4415. await previousData;
  4416. } else {
  4417. initParserData().catch(() => { /* ignored */ });
  4418. }
  4419. initPreviousData();
  4420. setInput(input);
  4421. return { done: false };
  4422. }
  4423. async function initParserData() {
  4424. let setValue;
  4425. value = new Promise(resolve => setValue = resolve);
  4426. parserData = new ParserData(consumeData);
  4427. initChunk();
  4428. const data = await parseValue(parserData);
  4429. parserData.executeSetters();
  4430. setValue(data);
  4431. }
  4432. function initChunk() {
  4433. input = new Promise(resolve => setInput = resolve);
  4434. }
  4435. function initPreviousData() {
  4436. previousData = new Promise(resolve => resolvePreviousData = resolve);
  4437. }
  4438. async function consumeData() {
  4439. const data = await input;
  4440. initChunk();
  4441. if (resolvePreviousData) {
  4442. resolvePreviousData();
  4443. }
  4444. return data;
  4445. }
  4446. }
  4447. async function parseValue(data) {
  4448. const array = await data.consume(1);
  4449. const parserType = array[0];
  4450. const parse = types[parserType].parse;
  4451. const valueId = data.getObjectId();
  4452. const result = await parse(data);
  4453. if (parserType != TYPE_REFERENCE && testObject(result)) {
  4454. await parseSymbols(data, result);
  4455. await parseOwnProperties(data, result);
  4456. }
  4457. data.resolveObject(valueId, result);
  4458. return result;
  4459. }
  4460. async function parseSymbols(data, value) {
  4461. const symbols = await parseArray(data);
  4462. data.setObject([symbols], symbols => symbols.forEach(([symbol, propertyValue]) => value[symbol] = propertyValue));
  4463. }
  4464. async function parseOwnProperties(data, object) {
  4465. const size = await parseValue(data);
  4466. if (size) {
  4467. await parseNextProperty();
  4468. }
  4469. async function parseNextProperty(indexKey = 0) {
  4470. const key = await parseString(data);
  4471. const value = await parseValue(data);
  4472. data.setObject([value], value => object[key] = value);
  4473. if (indexKey < size - 1) {
  4474. await parseNextProperty(indexKey + 1);
  4475. }
  4476. }
  4477. }
  4478. async function parseCircularReference(data) {
  4479. const index = await parseValue(data);
  4480. const result = new Reference(index, data);
  4481. return result;
  4482. }
  4483. function parseObject() {
  4484. return {};
  4485. }
  4486. async function parseArray(data) {
  4487. const length = await parseValue(data);
  4488. const array = new Array(length);
  4489. if (length) {
  4490. await parseNextSlot();
  4491. }
  4492. return array;
  4493. async function parseNextSlot(indexArray = 0) {
  4494. const value = await parseValue(data);
  4495. if (!testEmptySlot(value)) {
  4496. data.setObject([value], value => array[indexArray] = value);
  4497. }
  4498. if (indexArray < length - 1) {
  4499. await parseNextSlot(indexArray + 1);
  4500. }
  4501. }
  4502. }
  4503. function parseEmptySlot() {
  4504. return EMPTY_SLOT_VALUE;
  4505. }
  4506. async function parseString(data) {
  4507. const size = await parseValue(data);
  4508. const array = await data.consume(size);
  4509. return textDecoder.decode(array);
  4510. }
  4511. async function parseFloat64Array(data) {
  4512. const length = await parseValue(data);
  4513. const array = await data.consume(length * 8);
  4514. return new Float64Array(array.buffer);
  4515. }
  4516. async function parseFloat32Array(data) {
  4517. const length = await parseValue(data);
  4518. const array = await data.consume(length * 4);
  4519. return new Float32Array(array.buffer);
  4520. }
  4521. async function parseUint32Array(data) {
  4522. const length = await parseValue(data);
  4523. const array = await data.consume(length * 4);
  4524. return new Uint32Array(array.buffer);
  4525. }
  4526. async function parseInt32Array(data) {
  4527. const length = await parseValue(data);
  4528. const array = await data.consume(length * 4);
  4529. return new Int32Array(array.buffer);
  4530. }
  4531. async function parseUint16Array(data) {
  4532. const length = await parseValue(data);
  4533. const array = await data.consume(length * 2);
  4534. return new Uint16Array(array.buffer);
  4535. }
  4536. async function parseInt16Array(data) {
  4537. const length = await parseValue(data);
  4538. const array = await data.consume(length * 2);
  4539. return new Int16Array(array.buffer);
  4540. }
  4541. async function parseUint8ClampedArray(data) {
  4542. const length = await parseValue(data);
  4543. const array = await data.consume(length);
  4544. return new Uint8ClampedArray(array.buffer);
  4545. }
  4546. async function parseUint8Array(data) {
  4547. const length = await parseValue(data);
  4548. const array = await data.consume(length);
  4549. return array;
  4550. }
  4551. async function parseInt8Array(data) {
  4552. const length = await parseValue(data);
  4553. const array = await data.consume(length);
  4554. return new Int8Array(array.buffer);
  4555. }
  4556. async function parseArrayBuffer(data) {
  4557. const length = await parseValue(data);
  4558. const array = await data.consume(length);
  4559. return array.buffer;
  4560. }
  4561. async function parseNumber(data) {
  4562. const array = await data.consume(8);
  4563. return new Float64Array(array.buffer)[0];
  4564. }
  4565. async function parseUint32(data) {
  4566. const array = await data.consume(4);
  4567. return new Uint32Array(array.buffer)[0];
  4568. }
  4569. async function parseInt32(data) {
  4570. const array = await data.consume(4);
  4571. return new Int32Array(array.buffer)[0];
  4572. }
  4573. async function parseUint16(data) {
  4574. const array = await data.consume(2);
  4575. return new Uint16Array(array.buffer)[0];
  4576. }
  4577. async function parseInt16(data) {
  4578. const array = await data.consume(2);
  4579. return new Int16Array(array.buffer)[0];
  4580. }
  4581. async function parseUint8(data) {
  4582. const array = await data.consume(1);
  4583. return new Uint8Array(array.buffer)[0];
  4584. }
  4585. async function parseInt8(data) {
  4586. const array = await data.consume(1);
  4587. return new Int8Array(array.buffer)[0];
  4588. }
  4589. function parseUndefined() {
  4590. return undefined;
  4591. }
  4592. function parseNull() {
  4593. return null;
  4594. }
  4595. function parseNaN() {
  4596. return NaN;
  4597. }
  4598. async function parseBoolean(data) {
  4599. const array = await data.consume(1);
  4600. return Boolean(array[0]);
  4601. }
  4602. async function parseMap(data) {
  4603. const size = await parseValue(data);
  4604. const map = new Map();
  4605. if (size) {
  4606. await parseNextEntry();
  4607. }
  4608. return map;
  4609. async function parseNextEntry(indexKey = 0) {
  4610. const key = await parseValue(data);
  4611. const value = await parseValue(data);
  4612. data.setObject([key, value], (key, value) => map.set(key, value));
  4613. if (indexKey < size - 1) {
  4614. await parseNextEntry(indexKey + 1);
  4615. }
  4616. }
  4617. }
  4618. async function parseSet(data) {
  4619. const size = await parseValue(data);
  4620. const set = new Set();
  4621. if (size) {
  4622. await parseNextEntry();
  4623. }
  4624. return set;
  4625. async function parseNextEntry(indexKey = 0) {
  4626. const value = await parseValue(data);
  4627. data.setObject([value], value => set.add(value));
  4628. if (indexKey < size - 1) {
  4629. await parseNextEntry(indexKey + 1);
  4630. }
  4631. }
  4632. }
  4633. async function parseDate(data) {
  4634. const milliseconds = await parseNumber(data);
  4635. return new Date(milliseconds);
  4636. }
  4637. async function parseError(data) {
  4638. const message = await parseString(data);
  4639. const stack = await parseString(data);
  4640. const error = new Error(message);
  4641. error.stack = stack;
  4642. return error;
  4643. }
  4644. async function parseRegExp(data) {
  4645. const source = await parseString(data);
  4646. const flags = await parseString(data);
  4647. return new RegExp(source, flags);
  4648. }
  4649. async function parseStringObject(data) {
  4650. return new String(await parseString(data));
  4651. }
  4652. async function parseNumberObject(data) {
  4653. return new Number(await parseNumber(data));
  4654. }
  4655. async function parseBooleanObject(data) {
  4656. return new Boolean(await parseBoolean(data));
  4657. }
  4658. async function parseSymbol(data) {
  4659. const description = await parseString(data);
  4660. return Symbol(description);
  4661. }
  4662. function testCircularReference(value, data) {
  4663. return testObject(value) && data.objects.includes(value);
  4664. }
  4665. function testReference(value) {
  4666. return value instanceof Reference;
  4667. }
  4668. function testObject(value) {
  4669. return value === Object(value);
  4670. }
  4671. function testArray(value) {
  4672. return typeof value.length == "number";
  4673. }
  4674. function testEmptySlot(value) {
  4675. return value === EMPTY_SLOT_VALUE;
  4676. }
  4677. function testString(value) {
  4678. return typeof value == "string";
  4679. }
  4680. function testFloat64Array(value) {
  4681. return value.constructor.name == "Float64Array";
  4682. }
  4683. function testUint32Array(value) {
  4684. return value.constructor.name == "Uint32Array";
  4685. }
  4686. function testInt32Array(value) {
  4687. return value.constructor.name == "Int32Array";
  4688. }
  4689. function testUint16Array(value) {
  4690. return value.constructor.name == "Uint16Array";
  4691. }
  4692. function testFloat32Array(value) {
  4693. return value.constructor.name == "Float32Array";
  4694. }
  4695. function testInt16Array(value) {
  4696. return value.constructor.name == "Int16Array";
  4697. }
  4698. function testUint8ClampedArray(value) {
  4699. return value.constructor.name == "Uint8ClampedArray";
  4700. }
  4701. function testUint8Array(value) {
  4702. return value.constructor.name == "Uint8Array";
  4703. }
  4704. function testInt8Array(value) {
  4705. return value.constructor.name == "Int8Array";
  4706. }
  4707. function testArrayBuffer(value) {
  4708. return value.constructor.name == "ArrayBuffer";
  4709. }
  4710. function testNumber(value) {
  4711. return typeof value == "number";
  4712. }
  4713. function testUint32(value) {
  4714. return testInteger(value) && value >= 0 && value <= 4294967295;
  4715. }
  4716. function testInt32(value) {
  4717. return testInteger(value) && value >= -2147483648 && value <= 2147483647;
  4718. }
  4719. function testUint16(value) {
  4720. return testInteger(value) && value >= 0 && value <= 65535;
  4721. }
  4722. function testInt16(value) {
  4723. return testInteger(value) && value >= -32768 && value <= 32767;
  4724. }
  4725. function testUint8(value) {
  4726. return testInteger(value) && value >= 0 && value <= 255;
  4727. }
  4728. function testInt8(value) {
  4729. return testInteger(value) && value >= -128 && value <= 127;
  4730. }
  4731. function testInteger(value) {
  4732. return testNumber(value) && Number.isInteger(value);
  4733. }
  4734. function testUndefined(value) {
  4735. return value === undefined;
  4736. }
  4737. function testNull(value) {
  4738. return value === null;
  4739. }
  4740. function testNaN(value) {
  4741. return Number.isNaN(value);
  4742. }
  4743. function testBoolean(value) {
  4744. return typeof value == "boolean";
  4745. }
  4746. function testMap(value) {
  4747. return value instanceof Map;
  4748. }
  4749. function testSet(value) {
  4750. return value instanceof Set;
  4751. }
  4752. function testDate(value) {
  4753. return value instanceof Date;
  4754. }
  4755. function testError(value) {
  4756. return value instanceof Error;
  4757. }
  4758. function testRegExp(value) {
  4759. return value instanceof RegExp;
  4760. }
  4761. function testStringObject(value) {
  4762. return value instanceof String;
  4763. }
  4764. function testNumberObject(value) {
  4765. return value instanceof Number;
  4766. }
  4767. function testBooleanObject(value) {
  4768. return value instanceof Boolean;
  4769. }
  4770. function testSymbol(value) {
  4771. return typeof value == "symbol";
  4772. }
  4773. function testReferenceable(value) {
  4774. return testObject(value) || testSymbol(value);
  4775. }
  4776. /*
  4777. * Copyright 2010-2024 Gildas Lormeau
  4778. * contact : gildas.lormeau <at> gmail.com
  4779. * author: gildas.lormeau <at> gmail.com
  4780. * author: dcardin2007 <at> gmail.com
  4781. *
  4782. * This file is part of SingleFile.
  4783. *
  4784. * The code in this file is free software: you can redistribute it and/or
  4785. * modify it under the terms of the GNU Affero General Public License
  4786. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  4787. * of the License, or (at your option) any later version.
  4788. *
  4789. * The code in this file is distributed in the hope that it will be useful,
  4790. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  4791. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  4792. * General Public License for more details.
  4793. *
  4794. * As additional permission under GNU AGPL version 3 section 7, you may
  4795. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  4796. * AGPL normally required by section 4, provided you include this license
  4797. * notice and a URL through which recipients can access the Corresponding
  4798. * Source.
  4799. */
  4800. /* global fetch, btoa, Blob, FileReader, AbortController */
  4801. const AUTHORIZATION_HEADER = "Authorization";
  4802. const BEARER_PREFIX_AUTHORIZATION = "Bearer ";
  4803. const ACCEPT_HEADER = "Accept";
  4804. const CONTENT_TYPE = "multipart/form-data";
  4805. class RestFormApi {
  4806. constructor(token, restApiUrl, fileFieldName, urlFieldName) {
  4807. this.headers = new Map([
  4808. [AUTHORIZATION_HEADER, BEARER_PREFIX_AUTHORIZATION + token],
  4809. [ACCEPT_HEADER, CONTENT_TYPE]
  4810. ]);
  4811. this.restApiUrl = restApiUrl;
  4812. this.fileFieldName = fileFieldName;
  4813. this.urlFieldName = urlFieldName;
  4814. }
  4815. async upload(content, url) {
  4816. this.controller = new AbortController();
  4817. try{
  4818. const blob = new Blob([content], { type: 'text/html'});
  4819. const file = new File([blob], "SingleFile.html", { type: 'text/html' });
  4820. let formData = new FormData();
  4821. if(this.fileFieldName){
  4822. formData.append(this.fileFieldName, file);
  4823. }
  4824. if(this.urlFieldName){
  4825. formData.append(this.urlFieldName, url);
  4826. }
  4827. const response = await fetch(this.restApiUrl, {
  4828. method: 'POST',
  4829. body: formData,
  4830. headers: this.headers,
  4831. signal: this.controller.signal
  4832. });
  4833. if ([200,201].includes(response.status)) {
  4834. // do something with the data?
  4835. const data = await response.json();
  4836. } else {
  4837. throw new Error(await response.text());
  4838. }
  4839. }
  4840. catch(e){
  4841. throw new Error(e);
  4842. }
  4843. }
  4844. abort() {
  4845. if (this.controller) {
  4846. this.controller.abort();
  4847. }
  4848. }
  4849. }
  4850. /*
  4851. * Copyright 2010-2020 Gildas Lormeau
  4852. * contact : gildas.lormeau <at> gmail.com
  4853. *
  4854. * This file is part of SingleFile.
  4855. *
  4856. * The code in this file is free software: you can redistribute it and/or
  4857. * modify it under the terms of the GNU Affero General Public License
  4858. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  4859. * of the License, or (at your option) any later version.
  4860. *
  4861. * The code in this file is distributed in the hope that it will be useful,
  4862. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  4863. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  4864. * General Public License for more details.
  4865. *
  4866. * As additional permission under GNU AGPL version 3 section 7, you may
  4867. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  4868. * AGPL normally required by section 4, provided you include this license
  4869. * notice and a URL through which recipients can access the Corresponding
  4870. * Source.
  4871. */
  4872. const partialContents = new Map();
  4873. const tabData = new Map();
  4874. const SCOPES = ["https://www.googleapis.com/auth/drive.file"];
  4875. const CONFLICT_ACTION_SKIP = "skip";
  4876. const CONFLICT_ACTION_UNIQUIFY = "uniquify";
  4877. const REGEXP_ESCAPE = /([{}()^$&.*?/+|[\\\\]|\]|-)/g;
  4878. let GDRIVE_CLIENT_ID = "207618107333-h1220p1oasj3050kr5r416661adm091a.apps.googleusercontent.com";
  4879. let GDRIVE_CLIENT_KEY = "VQJ8Gq8Vxx72QyxPyeLtWvUt";
  4880. const DROPBOX_CLIENT_ID = "s50p6litdvuzrtb";
  4881. const DROPBOX_CLIENT_KEY = "i1vzwllesr14fzd";
  4882. const gDriveOauth2 = browser.runtime.getManifest().oauth2;
  4883. if (gDriveOauth2) {
  4884. GDRIVE_CLIENT_ID = gDriveOauth2.client_id;
  4885. GDRIVE_CLIENT_KEY = gDriveOauth2.client_secret;
  4886. }
  4887. const gDrive = new GDrive(GDRIVE_CLIENT_ID, GDRIVE_CLIENT_KEY, SCOPES);
  4888. const dropbox = new Dropbox(DROPBOX_CLIENT_ID, DROPBOX_CLIENT_KEY);
  4889. async function onMessage$4(message, sender) {
  4890. if (message.method.endsWith(".download")) {
  4891. return downloadTabPage(message, sender.tab);
  4892. }
  4893. if (message.method.endsWith(".disableGDrive")) {
  4894. const authInfo = await getAuthInfo$1();
  4895. removeAuthInfo();
  4896. await gDrive.revokeAuthToken(authInfo && (authInfo.accessToken || authInfo.revokableAccessToken));
  4897. return {};
  4898. }
  4899. if (message.method.endsWith(".disableDropbox")) {
  4900. const authInfo = await getDropboxAuthInfo$1();
  4901. removeDropboxAuthInfo();
  4902. await dropbox.revokeAuthToken(authInfo && (authInfo.accessToken || authInfo.revokableAccessToken));
  4903. return {};
  4904. }
  4905. if (message.method.endsWith(".end")) {
  4906. if (message.hash) {
  4907. try {
  4908. await anchor(message.hash, message.woleetKey);
  4909. } catch (error) {
  4910. onError(sender.tab.id, error.message, error.link);
  4911. }
  4912. }
  4913. onSaveEnd(message.taskId);
  4914. return {};
  4915. }
  4916. if (message.method.endsWith(".getInfo")) {
  4917. return getTasksInfo();
  4918. }
  4919. if (message.method.endsWith(".cancel")) {
  4920. cancelTask(message.taskId);
  4921. return {};
  4922. }
  4923. if (message.method.endsWith(".cancelAll")) {
  4924. cancelAllTasks();
  4925. return {};
  4926. }
  4927. if (message.method.endsWith(".saveUrls")) {
  4928. saveUrls(message.urls);
  4929. return {};
  4930. }
  4931. }
  4932. async function downloadTabPage(message, tab) {
  4933. const tabId = tab.id;
  4934. let contents;
  4935. if (message.blobURL) {
  4936. try {
  4937. if (message.compressContent) {
  4938. message.pageData = await parse(new Uint8Array(await (await fetch(message.blobURL)).arrayBuffer()));
  4939. await downloadCompressedContent(message, tab);
  4940. } else {
  4941. message.content = await (await fetch(message.blobURL)).text();
  4942. await downloadContent([message.content], tab, tab.incognito, message);
  4943. }
  4944. } catch (error) {
  4945. return { error: true };
  4946. }
  4947. } else if (message.compressContent) {
  4948. let blobParts = tabData.get(tabId);
  4949. const type = message.mimeType;
  4950. if (!blobParts) {
  4951. blobParts = [];
  4952. tabData.set(tabId, blobParts);
  4953. }
  4954. if (message.data) {
  4955. blobParts.push(new Uint8Array(message.data));
  4956. } else {
  4957. tabData.delete(tabId);
  4958. const message = await parse(new Uint8Array((await new Blob(blobParts, { type }).arrayBuffer())));
  4959. await downloadCompressedContent(message, tab);
  4960. }
  4961. } else {
  4962. if (message.truncated) {
  4963. contents = partialContents.get(tabId);
  4964. if (!contents) {
  4965. contents = [];
  4966. partialContents.set(tabId, contents);
  4967. }
  4968. contents.push(message.content);
  4969. if (message.finished) {
  4970. partialContents.delete(tabId);
  4971. }
  4972. } else if (message.content) {
  4973. contents = [message.content];
  4974. }
  4975. if (!message.truncated || message.finished) {
  4976. await downloadContent(contents, tab, tab.incognito, message);
  4977. }
  4978. }
  4979. return {};
  4980. }
  4981. async function downloadContent(contents, tab, incognito, message) {
  4982. const tabId = tab.id;
  4983. try {
  4984. let skipped;
  4985. if (message.backgroundSave && !message.saveToGDrive && !message.saveToDropbox && !message.saveWithWebDAV && !message.saveToGitHub && !message.saveToRestFormApi) {
  4986. const testSkip = await testSkipSave(message.filename, message);
  4987. message.filenameConflictAction = testSkip.filenameConflictAction;
  4988. skipped = testSkip.skipped;
  4989. }
  4990. if (skipped) {
  4991. onEnd(tabId);
  4992. } else {
  4993. const prompt = filename => promptFilename(tabId, filename);
  4994. let response;
  4995. if (message.openEditor) {
  4996. onEdit(tabId);
  4997. await open({ tabIndex: tab.index + 1, filename: message.filename, content: contents.join("") });
  4998. } else if (message.saveToClipboard) {
  4999. message.content = contents.join("");
  5000. saveToClipboard(message);
  5001. } else if (message.saveWithWebDAV) {
  5002. response = await saveWithWebDAV(message.taskId, encodeSharpCharacter(message.filename), contents.join(""), message.webDAVURL, message.webDAVUser, message.webDAVPassword, { filenameConflictAction: message.filenameConflictAction, prompt });
  5003. } else if (message.saveToGDrive) {
  5004. await saveToGDrive(message.taskId, encodeSharpCharacter(message.filename), new Blob(contents, { type: message.mimeType }), {
  5005. forceWebAuthFlow: message.forceWebAuthFlow
  5006. }, {
  5007. onProgress: (offset, size) => onUploadProgress(tabId, offset, size),
  5008. filenameConflictAction: message.filenameConflictAction,
  5009. prompt
  5010. });
  5011. } else if (message.saveToDropbox) {
  5012. await saveToDropbox(message.taskId, encodeSharpCharacter(message.filename), new Blob(contents, { type: message.mimeType }), {
  5013. onProgress: (offset, size) => onUploadProgress(tabId, offset, size),
  5014. filenameConflictAction: message.filenameConflictAction,
  5015. prompt
  5016. });
  5017. } else if (message.saveToGitHub) {
  5018. response = await saveToGitHub(message.taskId, encodeSharpCharacter(message.filename), contents.join(""), message.githubToken, message.githubUser, message.githubRepository, message.githubBranch, {
  5019. filenameConflictAction: message.filenameConflictAction,
  5020. prompt
  5021. });
  5022. await response.pushPromise;
  5023. } else if (message.saveWithCompanion) {
  5024. await save({
  5025. filename: message.filename,
  5026. content: message.content,
  5027. filenameConflictAction: message.filenameConflictAction
  5028. });
  5029. } else if (message.saveToRestFormApi) {
  5030. response = await saveToRestFormApi(
  5031. message.taskId,
  5032. contents.join(""),
  5033. tab.url,
  5034. message.saveToRestFormApiToken,
  5035. message.saveToRestFormApiUrl,
  5036. message.saveToRestFormApiFileFieldName,
  5037. message.saveToRestFormApiUrlFieldName
  5038. );
  5039. } else {
  5040. message.url = URL.createObjectURL(new Blob(contents, { type: message.mimeType }));
  5041. response = await downloadPage(message, {
  5042. confirmFilename: message.confirmFilename,
  5043. incognito,
  5044. filenameConflictAction: message.filenameConflictAction,
  5045. filenameReplacementCharacter: message.filenameReplacementCharacter,
  5046. bookmarkId: message.bookmarkId,
  5047. replaceBookmarkURL: message.replaceBookmarkURL,
  5048. includeInfobar: message.includeInfobar
  5049. });
  5050. }
  5051. if (message.bookmarkId && message.replaceBookmarkURL && response && response.url) {
  5052. await update(message.bookmarkId, { url: response.url });
  5053. }
  5054. onEnd(tabId);
  5055. if (message.openSavedPage && !message.openEditor) {
  5056. const createTabProperties = { active: true, url: "/src/ui/pages/viewer.html?blobURI=" + URL.createObjectURL(new Blob(contents, { type: message.mimeType })), windowId: tab.windowId };
  5057. if (tab.index != null) {
  5058. createTabProperties.index = tab.index + 1;
  5059. }
  5060. browser.tabs.create(createTabProperties);
  5061. }
  5062. }
  5063. } catch (error) {
  5064. if (!error.message || error.message != "upload_cancelled") {
  5065. console.error(error); // eslint-disable-line no-console
  5066. onError(tabId, error.message, error.link);
  5067. }
  5068. } finally {
  5069. if (message.url) {
  5070. URL.revokeObjectURL(message.url);
  5071. }
  5072. }
  5073. }
  5074. async function downloadCompressedContent(message, tab) {
  5075. const tabId = tab.id;
  5076. try {
  5077. let skipped;
  5078. if (message.backgroundSave && !message.saveToGDrive && !message.saveToDropbox && !message.saveWithWebDAV && !message.saveToGitHub && !message.sharePage) {
  5079. const testSkip = await testSkipSave(message.filename, message);
  5080. message.filenameConflictAction = testSkip.filenameConflictAction;
  5081. skipped = testSkip.skipped;
  5082. }
  5083. if (skipped) {
  5084. onEnd(tabId);
  5085. } else {
  5086. const pageData = message.pageData;
  5087. const prompt = filename => promptFilename(tabId, filename);
  5088. const blob = await singlefile.processors.compression.process(pageData, {
  5089. insertTextBody: message.insertTextBody,
  5090. url: pageData.url || tab.url,
  5091. createRootDirectory: message.createRootDirectory,
  5092. tabId,
  5093. selfExtractingArchive: message.selfExtractingArchive,
  5094. extractDataFromPage: message.extractDataFromPage,
  5095. preventAppendedData: message.preventAppendedData,
  5096. insertCanonicalLink: message.insertCanonicalLink,
  5097. insertMetaNoIndex: message.insertMetaNoIndex,
  5098. insertMetaCSP: message.insertMetaCSP,
  5099. password: message.password,
  5100. embeddedImage: message.embeddedImage
  5101. });
  5102. let response;
  5103. if (message.openEditor) {
  5104. onEdit(tabId);
  5105. await open({
  5106. tabIndex: tab.index + 1,
  5107. filename: message.filename,
  5108. content: Array.from(new Uint8Array(await blob.arrayBuffer())),
  5109. compressContent: message.compressContent,
  5110. selfExtractingArchive: message.selfExtractingArchive,
  5111. extractDataFromPage: message.extractDataFromPage,
  5112. insertTextBody: message.insertTextBody,
  5113. insertMetaCSP: message.insertMetaCSP,
  5114. embeddedImage: message.embeddedImage
  5115. });
  5116. } else if (message.foregroundSave || !message.backgroundSave || message.sharePage) {
  5117. const response = await downloadPageForeground(message.taskId, message.filename, blob, pageData.mimeType, tabId, {
  5118. foregroundSave: true,
  5119. sharePage: message.sharePage
  5120. });
  5121. if (response.error) {
  5122. throw new Error(response.error);
  5123. }
  5124. } else if (message.saveWithWebDAV) {
  5125. response = await saveWithWebDAV(message.taskId, encodeSharpCharacter(message.filename), blob, message.webDAVURL, message.webDAVUser, message.webDAVPassword, { filenameConflictAction: message.filenameConflictAction, prompt });
  5126. } else if (message.saveToGDrive) {
  5127. await saveToGDrive(message.taskId, encodeSharpCharacter(message.filename), blob, {
  5128. forceWebAuthFlow: message.forceWebAuthFlow
  5129. }, {
  5130. onProgress: (offset, size) => onUploadProgress(tabId, offset, size),
  5131. filenameConflictAction: message.filenameConflictAction,
  5132. prompt
  5133. });
  5134. } else if (message.saveToDropbox) {
  5135. await saveToDropbox(message.taskId, encodeSharpCharacter(message.filename), blob, {
  5136. onProgress: (offset, size) => onUploadProgress(tabId, offset, size),
  5137. filenameConflictAction: message.filenameConflictAction,
  5138. prompt
  5139. });
  5140. } else if (message.saveToGitHub) {
  5141. response = await saveToGitHub(message.taskId, encodeSharpCharacter(message.filename), blob, message.githubToken, message.githubUser, message.githubRepository, message.githubBranch, {
  5142. filenameConflictAction: message.filenameConflictAction,
  5143. prompt
  5144. });
  5145. await response.pushPromise;
  5146. } else {
  5147. message.url = URL.createObjectURL(blob);
  5148. response = await downloadPage(message, {
  5149. confirmFilename: message.confirmFilename,
  5150. incognito: tab.incognito,
  5151. filenameConflictAction: message.filenameConflictAction,
  5152. filenameReplacementCharacter: message.filenameReplacementCharacter,
  5153. bookmarkId: message.bookmarkId,
  5154. replaceBookmarkURL: message.replaceBookmarkURL,
  5155. includeInfobar: message.includeInfobar
  5156. });
  5157. }
  5158. if (message.bookmarkId && message.replaceBookmarkURL && response && response.url) {
  5159. await update(message.bookmarkId, { url: response.url });
  5160. }
  5161. onEnd(tabId);
  5162. if (message.openSavedPage && !message.openEditor) {
  5163. const createTabProperties = { active: true, url: "/src/ui/pages/viewer.html?compressed&blobURI=" + URL.createObjectURL(blob), windowId: tab.windowId };
  5164. if (tab.index != null) {
  5165. createTabProperties.index = tab.index + 1;
  5166. }
  5167. browser.tabs.create(createTabProperties);
  5168. }
  5169. }
  5170. } catch (error) {
  5171. if (!error.message || error.message != "upload_cancelled") {
  5172. console.error(error); // eslint-disable-line no-console
  5173. onError(tabId, error.message, error.link);
  5174. }
  5175. } finally {
  5176. if (message.url) {
  5177. URL.revokeObjectURL(message.url);
  5178. }
  5179. }
  5180. }
  5181. function encodeSharpCharacter(path) {
  5182. return path.replace(/#/g, "%23");
  5183. }
  5184. function getRegExp(string) {
  5185. return string.replace(REGEXP_ESCAPE, "\\$1");
  5186. }
  5187. async function getAuthInfo(authOptions, force) {
  5188. let authInfo = await getAuthInfo$1();
  5189. const options = {
  5190. interactive: true,
  5191. forceWebAuthFlow: authOptions.forceWebAuthFlow,
  5192. launchWebAuthFlow: options => launchWebAuthFlow(options),
  5193. extractAuthCode: authURL => extractAuthCode(authURL)
  5194. };
  5195. gDrive.setAuthInfo(authInfo, options);
  5196. if (!authInfo || !authInfo.accessToken || force) {
  5197. authInfo = await gDrive.auth(options);
  5198. if (authInfo) {
  5199. await setAuthInfo(authInfo);
  5200. } else {
  5201. await removeAuthInfo();
  5202. }
  5203. }
  5204. return authInfo;
  5205. }
  5206. async function getDropboxAuthInfo(force) {
  5207. let authInfo = await getDropboxAuthInfo$1();
  5208. const options = {
  5209. launchWebAuthFlow: options => launchWebAuthFlow(options),
  5210. extractAuthCode: authURL => extractAuthCode(authURL)
  5211. };
  5212. dropbox.setAuthInfo(authInfo);
  5213. if (!authInfo || !authInfo.accessToken || force) {
  5214. authInfo = await dropbox.auth(options);
  5215. if (authInfo) {
  5216. await setDropboxAuthInfo(authInfo);
  5217. } else {
  5218. await removeDropboxAuthInfo();
  5219. }
  5220. }
  5221. return authInfo;
  5222. }
  5223. async function saveToGitHub(taskId, filename, content, githubToken, githubUser, githubRepository, githubBranch, { filenameConflictAction, prompt }) {
  5224. try {
  5225. const taskInfo = getTaskInfo(taskId);
  5226. if (!taskInfo || !taskInfo.cancelled) {
  5227. const client = new GitHub(githubToken, githubUser, githubRepository, githubBranch);
  5228. setCancelCallback(taskId, () => client.abort());
  5229. return await client.upload(filename, content, { filenameConflictAction, prompt });
  5230. }
  5231. } catch (error) {
  5232. throw new Error(error.message + " (GitHub)");
  5233. }
  5234. }
  5235. async function saveWithWebDAV(taskId, filename, content, url, username, password, { filenameConflictAction, prompt }) {
  5236. try {
  5237. const taskInfo = getTaskInfo(taskId);
  5238. if (!taskInfo || !taskInfo.cancelled) {
  5239. const client = new WebDAV(url, username, password);
  5240. setCancelCallback(taskId, () => client.abort());
  5241. return await client.upload(filename, content, { filenameConflictAction, prompt });
  5242. }
  5243. } catch (error) {
  5244. throw new Error(error.message + " (WebDAV)");
  5245. }
  5246. }
  5247. async function saveToGDrive(taskId, filename, blob, authOptions, uploadOptions) {
  5248. try {
  5249. await getAuthInfo(authOptions);
  5250. const taskInfo = getTaskInfo(taskId);
  5251. if (!taskInfo || !taskInfo.cancelled) {
  5252. return await gDrive.upload(filename, blob, uploadOptions, callback => setCancelCallback(taskId, callback));
  5253. }
  5254. }
  5255. catch (error) {
  5256. if (error.message == "invalid_token") {
  5257. let authInfo;
  5258. try {
  5259. authInfo = await gDrive.refreshAuthToken();
  5260. } catch (error) {
  5261. if (error.message == "unknown_token") {
  5262. authInfo = await getAuthInfo(authOptions, true);
  5263. } else {
  5264. throw new Error(error.message + " (Google Drive)");
  5265. }
  5266. }
  5267. if (authInfo) {
  5268. await setAuthInfo(authInfo);
  5269. } else {
  5270. await removeAuthInfo();
  5271. }
  5272. return await saveToGDrive(taskId, filename, blob, authOptions, uploadOptions);
  5273. } else {
  5274. throw new Error(error.message + " (Google Drive)");
  5275. }
  5276. }
  5277. }
  5278. async function saveToDropbox(taskId, filename, blob, uploadOptions) {
  5279. try {
  5280. await getDropboxAuthInfo();
  5281. const taskInfo = getTaskInfo(taskId);
  5282. if (!taskInfo || !taskInfo.cancelled) {
  5283. return await dropbox.upload(filename, blob, uploadOptions, callback => setCancelCallback(taskId, callback));
  5284. }
  5285. }
  5286. catch (error) {
  5287. if (error.message == "invalid_token") {
  5288. let authInfo;
  5289. try {
  5290. authInfo = await dropbox.refreshAuthToken();
  5291. } catch (error) {
  5292. if (error.message == "unknown_token") {
  5293. authInfo = await getDropboxAuthInfo(true);
  5294. } else {
  5295. throw new Error(error.message + " (Dropbox)");
  5296. }
  5297. }
  5298. if (authInfo) {
  5299. await setDropboxAuthInfo(authInfo);
  5300. } else {
  5301. await removeDropboxAuthInfo();
  5302. }
  5303. return await saveToDropbox(taskId, filename, blob, uploadOptions);
  5304. } else {
  5305. throw new Error(error.message + " (Dropbox)");
  5306. }
  5307. }
  5308. }
  5309. async function testSkipSave(filename, options) {
  5310. let skipped, filenameConflictAction = options.filenameConflictAction;
  5311. if (filenameConflictAction == CONFLICT_ACTION_SKIP) {
  5312. const downloadItems = await browser.downloads.search({
  5313. filenameRegex: "(\\\\|/)" + getRegExp(filename) + "$",
  5314. exists: true
  5315. });
  5316. if (downloadItems.length) {
  5317. skipped = true;
  5318. } else {
  5319. filenameConflictAction = CONFLICT_ACTION_UNIQUIFY;
  5320. }
  5321. }
  5322. return { skipped, filenameConflictAction };
  5323. }
  5324. function promptFilename(tabId, filename) {
  5325. return browser.tabs.sendMessage(tabId, { method: "content.prompt", message: "Filename conflict, please enter a new filename", value: filename });
  5326. }
  5327. async function downloadPage(pageData, options) {
  5328. const downloadInfo = {
  5329. url: pageData.url,
  5330. saveAs: options.confirmFilename,
  5331. filename: pageData.filename,
  5332. conflictAction: options.filenameConflictAction
  5333. };
  5334. if (options.incognito) {
  5335. downloadInfo.incognito = true;
  5336. }
  5337. const downloadData = await download(downloadInfo, options.filenameReplacementCharacter);
  5338. if (downloadData.filename) {
  5339. let url = downloadData.filename;
  5340. if (!url.startsWith("file:")) {
  5341. if (url.startsWith("/")) {
  5342. url = url.substring(1);
  5343. }
  5344. url = "file:///" + encodeSharpCharacter(url);
  5345. }
  5346. return { url };
  5347. }
  5348. }
  5349. function saveToClipboard(pageData) {
  5350. const command = "copy";
  5351. document.addEventListener(command, listener);
  5352. document.execCommand(command);
  5353. document.removeEventListener(command, listener);
  5354. function listener(event) {
  5355. event.clipboardData.setData(pageData.mimeType, pageData.content);
  5356. event.clipboardData.setData("text/plain", pageData.content);
  5357. event.preventDefault();
  5358. }
  5359. }
  5360. async function saveToRestFormApi(taskId, content, url, token, restApiUrl, fileFieldName, urlFieldName) {
  5361. try {
  5362. const taskInfo = getTaskInfo(taskId);
  5363. if (!taskInfo || !taskInfo.cancelled) {
  5364. const client = new RestFormApi(token, restApiUrl, fileFieldName, urlFieldName);
  5365. setCancelCallback(taskId, () => client.abort());
  5366. return await client.upload(content, url);
  5367. }
  5368. } catch (error) {
  5369. throw new Error(error.message + " (RestFormApi)");
  5370. }
  5371. }
  5372. async function downloadPageForeground(taskId, filename, content, mimeType, tabId, { foregroundSave, sharePage }) {
  5373. const serializer = getSerializer({
  5374. filename,
  5375. taskId,
  5376. foregroundSave,
  5377. sharePage,
  5378. content: await content.arrayBuffer(),
  5379. mimeType
  5380. });
  5381. for await (const data of serializer) {
  5382. await browser.tabs.sendMessage(tabId, {
  5383. method: "content.download",
  5384. data: Array.from(data)
  5385. });
  5386. }
  5387. return browser.tabs.sendMessage(tabId, { method: "content.download" });
  5388. }
  5389. /*
  5390. * Copyright 2010-2020 Gildas Lormeau
  5391. * contact : gildas.lormeau <at> gmail.com
  5392. *
  5393. * This file is part of SingleFile.
  5394. *
  5395. * The code in this file is free software: you can redistribute it and/or
  5396. * modify it under the terms of the GNU Affero General Public License
  5397. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5398. * of the License, or (at your option) any later version.
  5399. *
  5400. * The code in this file is distributed in the hope that it will be useful,
  5401. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5402. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5403. * General Public License for more details.
  5404. *
  5405. * As additional permission under GNU AGPL version 3 section 7, you may
  5406. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5407. * AGPL normally required by section 4, provided you include this license
  5408. * notice and a URL through which recipients can access the Corresponding
  5409. * Source.
  5410. */
  5411. const pendingMessages = {};
  5412. const replacedTabIds = {};
  5413. async function onMessage$3(message, sender) {
  5414. if (message.method.endsWith(".save")) {
  5415. if (message.autoSaveDiscard || message.autoSaveRemove) {
  5416. if (sender.tab) {
  5417. message.tab = sender.tab;
  5418. pendingMessages[sender.tab.id] = message;
  5419. } else if (pendingMessages[message.tabId] &&
  5420. ((pendingMessages[message.tabId].removed && message.autoSaveRemove) ||
  5421. (pendingMessages[message.tabId].discarded && message.autoSaveDiscard))
  5422. ) {
  5423. delete pendingMessages[message.tabId];
  5424. await saveContent(message, { id: message.tabId, index: message.tabIndex, url: sender.url });
  5425. }
  5426. if (message.autoSaveUnload) {
  5427. delete pendingMessages[message.tabId];
  5428. await saveContent(message, sender.tab);
  5429. }
  5430. } else {
  5431. delete pendingMessages[message.tabId];
  5432. await saveContent(message, sender.tab);
  5433. }
  5434. return {};
  5435. }
  5436. }
  5437. function onTabUpdated$1(tabId) {
  5438. delete pendingMessages[tabId];
  5439. }
  5440. async function onTabRemoved$1(tabId) {
  5441. const message = pendingMessages[tabId];
  5442. if (message) {
  5443. if (message.autoSaveRemove) {
  5444. delete pendingMessages[tabId];
  5445. await saveContent(message, message.tab);
  5446. }
  5447. } else {
  5448. pendingMessages[tabId] = { removed: true };
  5449. }
  5450. }
  5451. async function onTabDiscarded(tabId) {
  5452. const message = pendingMessages[tabId];
  5453. if (message) {
  5454. delete pendingMessages[tabId];
  5455. await saveContent(message, message.tab);
  5456. } else {
  5457. pendingMessages[tabId] = { discarded: true };
  5458. }
  5459. }
  5460. async function onTabReplaced$1(addedTabId, removedTabId) {
  5461. if (pendingMessages[removedTabId] && !pendingMessages[addedTabId]) {
  5462. pendingMessages[addedTabId] = pendingMessages[removedTabId];
  5463. delete pendingMessages[removedTabId];
  5464. replacedTabIds[removedTabId] = addedTabId;
  5465. }
  5466. }
  5467. async function onMessageExternal(message, currentTab) {
  5468. if (message.method == "enableAutoSave") {
  5469. const allTabsData = await getPersistent(currentTab.id);
  5470. allTabsData[currentTab.id].autoSave = message.enabled;
  5471. await setPersistent(allTabsData);
  5472. refreshTab(currentTab);
  5473. }
  5474. if (message.method == "isAutoSaveEnabled") {
  5475. return autoSaveIsEnabled(currentTab);
  5476. }
  5477. }
  5478. async function onInit$1(tab) {
  5479. const [options, autoSaveEnabled] = await Promise.all([getOptions(tab.url, true), autoSaveIsEnabled(tab)]);
  5480. if (options && ((options.autoSaveLoad || options.autoSaveLoadOrUnload) && autoSaveEnabled)) {
  5481. saveTabs([tab], { autoSave: true });
  5482. }
  5483. }
  5484. async function saveContent(message, tab) {
  5485. const tabId = tab.id;
  5486. const options = await getOptions(tab.url, true);
  5487. if (options) {
  5488. onStart(tabId, 1, true);
  5489. options.content = message.content;
  5490. options.url = message.url;
  5491. options.frames = message.frames;
  5492. options.canvases = message.canvases;
  5493. options.fonts = message.fonts;
  5494. options.stylesheets = message.stylesheets;
  5495. options.images = message.images;
  5496. options.posters = message.posters;
  5497. options.videos = message.videos;
  5498. options.usedFonts = message.usedFonts;
  5499. options.shadowRoots = message.shadowRoots;
  5500. options.referrer = message.referrer;
  5501. options.updatedResources = message.updatedResources;
  5502. options.adoptedStyleSheets = message.adoptedStyleSheets;
  5503. options.visitDate = new Date(message.visitDate);
  5504. options.backgroundTab = true;
  5505. options.autoSave = true;
  5506. options.incognito = tab.incognito;
  5507. options.tabId = tabId;
  5508. options.tabIndex = tab.index;
  5509. options.keepFilename = options.saveToGDrive || options.saveToGitHub || options.saveWithWebDAV || options.saveToDropbox || options.saveToRestFormApi;
  5510. let pageData;
  5511. try {
  5512. if (options.autoSaveExternalSave) {
  5513. await externalSave(options);
  5514. } else {
  5515. if (options.passReferrerOnError) {
  5516. enableReferrerOnError();
  5517. }
  5518. options.tabId = tabId;
  5519. pageData = await getPageData(options, null, null, { fetch: fetch$1 });
  5520. let skipped;
  5521. if (!options.saveToGDrive && !options.saveWithWebDAV && !options.saveToGitHub && !options.saveToDropbox && !options.saveWithCompanion && !options.saveToRestFormApi) {
  5522. const testSkip = await testSkipSave(pageData.filename, options);
  5523. skipped = testSkip.skipped;
  5524. options.filenameConflictAction = testSkip.filenameConflictAction;
  5525. }
  5526. if (!skipped) {
  5527. let { content, mimeType: type } = pageData;
  5528. if (options.compressContent) {
  5529. content = new Blob([new Uint8Array(content)], { type });
  5530. }
  5531. if (options.saveToGDrive) {
  5532. if (!(content instanceof Blob)) {
  5533. content = new Blob([content], { type });
  5534. }
  5535. await saveToGDrive(message.taskId, encodeSharpCharacter(pageData.filename), content, options, {
  5536. forceWebAuthFlow: options.forceWebAuthFlow
  5537. }, {
  5538. filenameConflictAction: options.filenameConflictAction
  5539. });
  5540. } if (options.saveToDropbox) {
  5541. if (!(content instanceof Blob)) {
  5542. content = new Blob([content], { type });
  5543. }
  5544. await saveToDropbox(message.taskId, encodeSharpCharacter(pageData.filename), content, {
  5545. filenameConflictAction: options.filenameConflictAction
  5546. });
  5547. } else if (options.saveWithWebDAV) {
  5548. await saveWithWebDAV(message.taskId, encodeSharpCharacter(pageData.filename), content, options.webDAVURL, options.webDAVUser, options.webDAVPassword, {
  5549. filenameConflictAction: options.filenameConflictAction
  5550. });
  5551. } else if (options.saveToGitHub) {
  5552. await (await saveToGitHub(message.taskId, encodeSharpCharacter(pageData.filename), content, options.githubToken, options.githubUser, options.githubRepository, options.githubBranch, {
  5553. filenameConflictAction: options.filenameConflictAction
  5554. })).pushPromise;
  5555. } else if (options.saveWithCompanion && !options.compressContent) {
  5556. await save({
  5557. filename: pageData.filename,
  5558. content: pageData.content,
  5559. filenameConflictAction: options.filenameConflictAction
  5560. });
  5561. } else if (options.saveToRestFormApi) {
  5562. await saveToRestFormApi(
  5563. message.taskId,
  5564. content,
  5565. pageData.url,
  5566. options.restFormApiToken,
  5567. options.restFormApiUrl,
  5568. options.restFormApiFileFieldName,
  5569. options.restFormApiUrlFieldName
  5570. );
  5571. } else {
  5572. if (!(content instanceof Blob)) {
  5573. content = new Blob([content], { type });
  5574. }
  5575. pageData.url = URL.createObjectURL(content);
  5576. await downloadPage(pageData, options);
  5577. }
  5578. if (options.openSavedPage) {
  5579. const createTabProperties = { active: true, url: "/src/ui/pages/viewer.html?compressed=true&blobURI=" + URL.createObjectURL(content), windowId: tab.windowId };
  5580. const index = tab.index;
  5581. try {
  5582. await browser.tabs.get(tabId);
  5583. createTabProperties.index = index + 1;
  5584. } catch (error) {
  5585. createTabProperties.index = index;
  5586. }
  5587. browser.tabs.create(createTabProperties);
  5588. }
  5589. if (pageData.hash) {
  5590. await anchor(pageData.hash, options.woleetKey);
  5591. }
  5592. }
  5593. }
  5594. } finally {
  5595. if (message.taskId) {
  5596. onSaveEnd(message.taskId);
  5597. } else if (options.autoClose) {
  5598. browser.tabs.remove(replacedTabIds[tabId] || tabId);
  5599. delete replacedTabIds[tabId];
  5600. }
  5601. if (pageData && pageData.url) {
  5602. URL.revokeObjectURL(pageData.url);
  5603. }
  5604. onEnd(tabId, true);
  5605. }
  5606. }
  5607. }
  5608. async function fetch$1(url, options = {}) {
  5609. const response = await fetchResource$1(url, options);
  5610. return {
  5611. status: response.status,
  5612. headers: {
  5613. get: name => response.headers.get(name)
  5614. },
  5615. arrayBuffer: () => response.arrayBuffer
  5616. };
  5617. }
  5618. /*
  5619. * Copyright 2010-2020 Gildas Lormeau
  5620. * contact : gildas.lormeau <at> gmail.com
  5621. *
  5622. * This file is part of SingleFile.
  5623. *
  5624. * The code in this file is free software: you can redistribute it and/or
  5625. * modify it under the terms of the GNU Affero General Public License
  5626. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5627. * of the License, or (at your option) any later version.
  5628. *
  5629. * The code in this file is distributed in the hope that it will be useful,
  5630. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5631. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5632. * General Public License for more details.
  5633. *
  5634. * As additional permission under GNU AGPL version 3 section 7, you may
  5635. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5636. * AGPL normally required by section 4, provided you include this license
  5637. * notice and a URL through which recipients can access the Corresponding
  5638. * Source.
  5639. */
  5640. async function onMessage$2(message) {
  5641. if (message.method.endsWith(".resourceCommitted")) {
  5642. if (message.tabId && message.url && (message.type == "stylesheet" || message.type == "script")) {
  5643. await browser.tabs.sendMessage(message.tabId, message);
  5644. }
  5645. }
  5646. }
  5647. /*
  5648. * Copyright 2010-2020 Gildas Lormeau
  5649. * contact : gildas.lormeau <at> gmail.com
  5650. *
  5651. * This file is part of SingleFile.
  5652. *
  5653. * The code in this file is free software: you can redistribute it and/or
  5654. * modify it under the terms of the GNU Affero General Public License
  5655. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5656. * of the License, or (at your option) any later version.
  5657. *
  5658. * The code in this file is distributed in the hope that it will be useful,
  5659. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5660. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5661. * General Public License for more details.
  5662. *
  5663. * As additional permission under GNU AGPL version 3 section 7, you may
  5664. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5665. * AGPL normally required by section 4, provided you include this license
  5666. * notice and a URL through which recipients can access the Corresponding
  5667. * Source.
  5668. */
  5669. const DELAY_MAYBE_INIT = 1500;
  5670. browser.tabs.onCreated.addListener(tab => onTabCreated(tab));
  5671. browser.tabs.onActivated.addListener(activeInfo => onTabActivated(activeInfo));
  5672. browser.tabs.onRemoved.addListener(tabId => onTabRemoved(tabId));
  5673. browser.tabs.onUpdated.addListener((tabId, changeInfo) => onTabUpdated(tabId, changeInfo));
  5674. browser.tabs.onReplaced.addListener((addedTabId, removedTabId) => onTabReplaced(addedTabId, removedTabId));
  5675. async function onMessage$1(message, sender) {
  5676. if (message.method.endsWith(".init")) {
  5677. await onInit(sender.tab, message);
  5678. onInit$3(sender.tab);
  5679. onInit$2(sender.tab);
  5680. onInit$1(sender.tab);
  5681. }
  5682. if (message.method.endsWith(".getOptions")) {
  5683. return getOptions(message.url);
  5684. }
  5685. if (message.method.endsWith(".activate")) {
  5686. await browser.tabs.update(message.tabId, { active: true });
  5687. }
  5688. if (message.method.endsWith(".getScreenshot")) {
  5689. return captureTab(sender.tab.id, message);
  5690. }
  5691. }
  5692. async function onInit(tab, options) {
  5693. await remove(tab.id);
  5694. const allTabsData = await getPersistent(tab.id);
  5695. allTabsData[tab.id].savedPageDetected = options.savedPageDetected;
  5696. await setPersistent(allTabsData);
  5697. }
  5698. async function onTabUpdated(tabId, changeInfo) {
  5699. if (changeInfo.status == "complete") {
  5700. setTimeout(async () => {
  5701. try {
  5702. await browser.tabs.sendMessage(tabId, { method: "content.maybeInit" });
  5703. }
  5704. catch (error) {
  5705. // ignored
  5706. }
  5707. }, DELAY_MAYBE_INIT);
  5708. onTabUpdated$1(tabId);
  5709. const tab = await browser.tabs.get(tabId);
  5710. if (isEditor(tab)) {
  5711. const allTabsData = await getPersistent(tab.id);
  5712. allTabsData[tab.id].editorDetected = true;
  5713. await setPersistent(allTabsData);
  5714. onTabActivated$1(tab);
  5715. }
  5716. }
  5717. if (changeInfo.discarded) {
  5718. onTabDiscarded(tabId);
  5719. }
  5720. }
  5721. function onTabReplaced(addedTabId, removedTabId) {
  5722. onTabReplaced$3(addedTabId, removedTabId);
  5723. onTabReplaced$1(addedTabId, removedTabId);
  5724. onTabReplaced$2(addedTabId, removedTabId);
  5725. }
  5726. function onTabCreated(tab) {
  5727. onTabCreated$1(tab);
  5728. }
  5729. async function onTabActivated(activeInfo) {
  5730. const tab = await browser.tabs.get(activeInfo.tabId);
  5731. onTabActivated$1(tab);
  5732. }
  5733. function onTabRemoved(tabId) {
  5734. remove(tabId);
  5735. onTabRemoved$2(tabId);
  5736. cancelTab(tabId);
  5737. onTabRemoved$1(tabId);
  5738. }
  5739. async function captureTab(tabId, options) {
  5740. const { width, height } = options;
  5741. const canvas = new OffscreenCanvas(width, height);
  5742. const context = canvas.getContext("2d");
  5743. const image = new Image();
  5744. let y = 0, scrollYStep, activeTabId;
  5745. if (browser.tabs.captureTab) {
  5746. scrollYStep = 4 * 1024;
  5747. } else {
  5748. scrollYStep = options.innerHeight;
  5749. activeTabId = (await browser.tabs.query({ active: true, currentWindow: true }))[0].id;
  5750. await browser.tabs.sendMessage(tabId, { method: "content.beginScrollTo" });
  5751. }
  5752. while (y < height) {
  5753. let imageSrc;
  5754. if (browser.tabs.captureTab) {
  5755. imageSrc = await browser.tabs.captureTab(tabId, {
  5756. format: "png",
  5757. rect: { x: 0, y, width, height: Math.min(height - y, scrollYStep) }
  5758. });
  5759. } else {
  5760. await browser.tabs.sendMessage(tabId, { method: "content.scrollTo", y });
  5761. await browser.tabs.update(tabId, { active: true });
  5762. imageSrc = await browser.tabs.captureVisibleTab(null, {
  5763. format: "png"
  5764. });
  5765. }
  5766. await new Promise((resolve, reject) => {
  5767. image.onload = resolve;
  5768. image.onerror = event => reject(new Error(event.detail));
  5769. image.src = imageSrc;
  5770. });
  5771. context.drawImage(image, 0, y, width, Math.min(height - y, scrollYStep));
  5772. y += scrollYStep;
  5773. }
  5774. if (!browser.tabs.captureTab) {
  5775. await browser.tabs.update(activeTabId, { active: true });
  5776. await browser.tabs.sendMessage(tabId, { method: "content.endScrollTo" });
  5777. }
  5778. const blob = await canvas.convertToBlob({ type: "image/png" });
  5779. return URL.createObjectURL(blob);
  5780. }
  5781. /*
  5782. * Copyright 2010-2020 Gildas Lormeau
  5783. * contact : gildas.lormeau <at> gmail.com
  5784. *
  5785. * This file is part of SingleFile.
  5786. *
  5787. * The code in this file is free software: you can redistribute it and/or
  5788. * modify it under the terms of the GNU Affero General Public License
  5789. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5790. * of the License, or (at your option) any later version.
  5791. *
  5792. * The code in this file is distributed in the hope that it will be useful,
  5793. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5794. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5795. * General Public License for more details.
  5796. *
  5797. * As additional permission under GNU AGPL version 3 section 7, you may
  5798. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5799. * AGPL normally required by section 4, provided you include this license
  5800. * notice and a URL through which recipients can access the Corresponding
  5801. * Source.
  5802. */
  5803. /* global browser */
  5804. browser.runtime.onMessage.addListener((message, sender) => {
  5805. if (message.method == "singlefile.frameTree.initResponse" || message.method == "singlefile.frameTree.ackInitRequest") {
  5806. browser.tabs.sendMessage(sender.tab.id, message, { frameId: 0 });
  5807. return Promise.resolve({});
  5808. }
  5809. });
  5810. /*
  5811. * Copyright 2010-2020 Gildas Lormeau
  5812. * contact : gildas.lormeau <at> gmail.com
  5813. *
  5814. * This file is part of SingleFile.
  5815. *
  5816. * The code in this file is free software: you can redistribute it and/or
  5817. * modify it under the terms of the GNU Affero General Public License
  5818. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5819. * of the License, or (at your option) any later version.
  5820. *
  5821. * The code in this file is distributed in the hope that it will be useful,
  5822. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5823. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5824. * General Public License for more details.
  5825. *
  5826. * As additional permission under GNU AGPL version 3 section 7, you may
  5827. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5828. * AGPL normally required by section 4, provided you include this license
  5829. * notice and a URL through which recipients can access the Corresponding
  5830. * Source.
  5831. */
  5832. /* global browser, setTimeout, clearTimeout */
  5833. const timeouts = new Map();
  5834. browser.runtime.onMessage.addListener((message, sender) => {
  5835. if (message.method == "singlefile.lazyTimeout.setTimeout") {
  5836. let tabTimeouts = timeouts.get(sender.tab.id);
  5837. let frameTimeouts;
  5838. if (tabTimeouts) {
  5839. frameTimeouts = tabTimeouts.get(sender.frameId);
  5840. if (frameTimeouts) {
  5841. const previousTimeoutId = frameTimeouts.get(message.type);
  5842. if (previousTimeoutId) {
  5843. clearTimeout(previousTimeoutId);
  5844. }
  5845. } else {
  5846. frameTimeouts = new Map();
  5847. }
  5848. }
  5849. const timeoutId = setTimeout(async () => {
  5850. try {
  5851. const tabTimeouts = timeouts.get(sender.tab.id);
  5852. const frameTimeouts = tabTimeouts.get(sender.frameId);
  5853. if (tabTimeouts && frameTimeouts) {
  5854. deleteTimeout(frameTimeouts, message.type);
  5855. }
  5856. await browser.tabs.sendMessage(sender.tab.id, { method: "singlefile.lazyTimeout.onTimeout", type: message.type });
  5857. } catch (error) {
  5858. // ignored
  5859. }
  5860. }, message.delay);
  5861. if (!tabTimeouts) {
  5862. tabTimeouts = new Map();
  5863. frameTimeouts = new Map();
  5864. tabTimeouts.set(sender.frameId, frameTimeouts);
  5865. timeouts.set(sender.tab.id, tabTimeouts);
  5866. }
  5867. frameTimeouts.set(message.type, timeoutId);
  5868. return Promise.resolve({});
  5869. }
  5870. if (message.method == "singlefile.lazyTimeout.clearTimeout") {
  5871. let tabTimeouts = timeouts.get(sender.tab.id);
  5872. if (tabTimeouts) {
  5873. const frameTimeouts = tabTimeouts.get(sender.frameId);
  5874. if (frameTimeouts) {
  5875. const timeoutId = frameTimeouts.get(message.type);
  5876. if (timeoutId) {
  5877. clearTimeout(timeoutId);
  5878. }
  5879. deleteTimeout(frameTimeouts, message.type);
  5880. }
  5881. }
  5882. return Promise.resolve({});
  5883. }
  5884. });
  5885. browser.tabs.onRemoved.addListener(tabId => timeouts.delete(tabId));
  5886. function deleteTimeout(framesTimeouts, type) {
  5887. framesTimeouts.delete(type);
  5888. }
  5889. /*
  5890. * Copyright 2010-2020 Gildas Lormeau
  5891. * contact : gildas.lormeau <at> gmail.com
  5892. *
  5893. * This file is part of SingleFile.
  5894. *
  5895. * The code in this file is free software: you can redistribute it and/or
  5896. * modify it under the terms of the GNU Affero General Public License
  5897. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5898. * of the License, or (at your option) any later version.
  5899. *
  5900. * The code in this file is distributed in the hope that it will be useful,
  5901. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5902. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5903. * General Public License for more details.
  5904. *
  5905. * As additional permission under GNU AGPL version 3 section 7, you may
  5906. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5907. * AGPL normally required by section 4, provided you include this license
  5908. * notice and a URL through which recipients can access the Corresponding
  5909. * Source.
  5910. */
  5911. const ACTION_SAVE_PAGE = "save-page";
  5912. const ACTION_EDIT_AND_SAVE_PAGE = "edit-and-save-page";
  5913. const ACTION_SAVE_SELECTED_LINKS = "save-selected-links";
  5914. const ACTION_SAVE_SELECTED = "save-selected-content";
  5915. const ACTION_SAVE_SELECTED_TABS = "save-selected-tabs";
  5916. const ACTION_SAVE_UNPINNED_TABS = "save-unpinned-tabs";
  5917. const ACTION_SAVE_ALL_TABS = "save-all-tabs";
  5918. async function onMessage(message, sender) {
  5919. if (message == ACTION_SAVE_PAGE) {
  5920. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  5921. tabs.length = 1;
  5922. await saveTabs(tabs);
  5923. } else if (message == ACTION_EDIT_AND_SAVE_PAGE) {
  5924. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  5925. tabs.length = 1;
  5926. await saveTabs(tabs, { openEditor: true });
  5927. } else if (message == ACTION_SAVE_SELECTED_LINKS) {
  5928. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  5929. await saveSelectedLinks(tabs[0]);
  5930. } else if (message == ACTION_SAVE_SELECTED) {
  5931. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  5932. await saveTabs(tabs, { selected: true });
  5933. } else if (message == ACTION_SAVE_SELECTED_TABS) {
  5934. const tabs = await queryTabs({ currentWindow: true, highlighted: true });
  5935. await saveTabs(tabs);
  5936. } else if (message == ACTION_SAVE_UNPINNED_TABS) {
  5937. const tabs = await queryTabs({ currentWindow: true, pinned: false });
  5938. await saveTabs(tabs);
  5939. } else if (message == ACTION_SAVE_ALL_TABS) {
  5940. const tabs = await queryTabs({ currentWindow: true });
  5941. await saveTabs(tabs);
  5942. } else if (message.method) {
  5943. const tabs = await browser.tabs.query({ currentWindow: true, active: true });
  5944. const currentTab = tabs[0];
  5945. if (currentTab) {
  5946. return onMessageExternal(message, currentTab);
  5947. } else {
  5948. return false;
  5949. }
  5950. }
  5951. }
  5952. async function queryTabs(options) {
  5953. const tabs = await browser.tabs.query(options);
  5954. return tabs.sort((tab1, tab2) => tab1.index - tab2.index);
  5955. }
  5956. /*
  5957. * Copyright 2010-2020 Gildas Lormeau
  5958. * contact : gildas.lormeau <at> gmail.com
  5959. *
  5960. * This file is part of SingleFile.
  5961. *
  5962. * The code in this file is free software: you can redistribute it and/or
  5963. * modify it under the terms of the GNU Affero General Public License
  5964. * (GNU AGPL) as published by the Free Software Foundation, either version 3
  5965. * of the License, or (at your option) any later version.
  5966. *
  5967. * The code in this file is distributed in the hope that it will be useful,
  5968. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  5969. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  5970. * General Public License for more details.
  5971. *
  5972. * As additional permission under GNU AGPL version 3 section 7, you may
  5973. * distribute UNMODIFIED VERSIONS OF THIS file without the copy of the GNU
  5974. * AGPL normally required by section 4, provided you include this license
  5975. * notice and a URL through which recipients can access the Corresponding
  5976. * Source.
  5977. */
  5978. browser.runtime.onMessage.addListener((message, sender) => {
  5979. if (message.method.startsWith("tabs.")) {
  5980. return onMessage$1(message, sender);
  5981. }
  5982. if (message.method.startsWith("downloads.")) {
  5983. return onMessage$4(message, sender);
  5984. }
  5985. if (message.method.startsWith("autosave.")) {
  5986. return onMessage$3(message, sender);
  5987. }
  5988. if (message.method.startsWith("ui.")) {
  5989. return onMessage$7(message, sender);
  5990. }
  5991. if (message.method.startsWith("config.")) {
  5992. return onMessage$d(message);
  5993. }
  5994. if (message.method.startsWith("tabsData.")) {
  5995. return onMessage$e(message);
  5996. }
  5997. if (message.method.startsWith("devtools.")) {
  5998. return onMessage$2(message);
  5999. }
  6000. if (message.method.startsWith("editor.")) {
  6001. return onMessage$b(message, sender);
  6002. }
  6003. if (message.method.startsWith("bookmarks.")) {
  6004. return onMessage$5(message);
  6005. }
  6006. if (message.method.startsWith("companion.")) {
  6007. return onMessage$6(message);
  6008. }
  6009. if (message.method.startsWith("requests.")) {
  6010. return onMessage$a(message);
  6011. }
  6012. if (message.method.startsWith("bootstrap.")) {
  6013. return onMessage$c(message, sender);
  6014. }
  6015. });
  6016. if (browser.runtime.onMessageExternal) {
  6017. browser.runtime.onMessageExternal.addListener(onMessage);
  6018. }
  6019. })();