DriveClient.js 76 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695
  1. /**
  2. * Copyright (c) 2006-2024, JGraph Ltd
  3. * Copyright (c) 2006-2024, draw.io AG
  4. */
  5. //Add a closure to hide the class private variables without changing the code a lot
  6. (function()
  7. {
  8. var _token = null;
  9. var pickers = {};
  10. window.DriveClient = function(editorUi, isExtAuth)
  11. {
  12. if (isExtAuth == null && window.urlParams != null && window.urlParams['extAuth'] == '1')
  13. {
  14. isExtAuth = true;
  15. }
  16. mxEventSource.call(this);
  17. DrawioClient.call(this, editorUi, 'gDriveAuthInfo');
  18. this.isExtAuth = isExtAuth;
  19. /**
  20. * Holds a reference to the UI. Needed for the sharing client.
  21. */
  22. this.ui = editorUi;
  23. // New mime type for XML files
  24. this.xmlMimeType = 'application/vnd.jgraph.mxfile';
  25. this.mimeType = 'application/vnd.jgraph.mxfile.realtime';
  26. // Reading files now possible with no initial click in drive
  27. //TODO In teams we do auth using editor app, we need to support viewer only app also
  28. if (this.ui.editor.chromeless && !this.ui.editor.editable && urlParams['rt'] != '1' && urlParams['extAuth'] != '1')
  29. {
  30. // Uses separate name for the viewer auth tokens
  31. this.cookieName = 'gDriveViewerAuthInfo';
  32. this.token = this.getPersistentToken();
  33. }
  34. this.appId = window.DRAWIO_GOOGLE_APP_ID || '671128082532';
  35. this.clientId = window.DRAWIO_GOOGLE_CLIENT_ID || '671128082532-jhphbq6d0e1gnsus9mn7vf8a6fjn10mp.apps.googleusercontent.com';
  36. this.mimeTypes = this.xmlMimeType + ',application/mxe,application/mxr,' +
  37. 'application/vnd.jgraph.mxfile.realtime,application/vnd.jgraph.mxfile.rtlegacy';
  38. var authInfo = JSON.parse(this.token);
  39. if (authInfo != null && authInfo.current != null)
  40. {
  41. this.userId = authInfo.current.userId;
  42. this.authCalled = false;
  43. }
  44. };
  45. // Extends mxEventSource
  46. mxUtils.extend(DriveClient, mxEventSource);
  47. // Extends DrawioClient
  48. mxUtils.extend(DriveClient, DrawioClient);
  49. DriveClient.prototype.redirectUri = window.DRAWIO_SERVER_URL + 'google';
  50. DriveClient.prototype.GDriveBaseUrl = 'https://www.googleapis.com/drive/v2';
  51. /**
  52. * OAuth 2.0 scopes for installing Drive Apps.
  53. */
  54. DriveClient.prototype.scopes = ['https://www.googleapis.com/auth/drive.file',
  55. 'https://www.googleapis.com/auth/drive.install',
  56. 'https://www.googleapis.com/auth/userinfo.profile'];
  57. /**
  58. * Contains the hostname of the old app.
  59. */
  60. DriveClient.prototype.allFields = 'kind,id,parents,headRevisionId,etag,title,mimeType,modifiedDate,' +
  61. 'editable,copyable,canComment,labels,properties,downloadUrl,webContentLink,userPermission,fileSize';
  62. /**
  63. * Fields required for catchin up.
  64. *
  65. * TODO: Limit to etag and ekey property only
  66. */
  67. DriveClient.prototype.catchupFields = 'etag,headRevisionId,modifiedDate,properties(key,value)';
  68. /**
  69. * Specifies if thumbnails should be enabled. Default is true.
  70. * LATER: If thumbnails are disabled, make sure to replace the
  71. * existing thumbnail with the placeholder only once.
  72. */
  73. DriveClient.prototype.enableThumbnails = true;
  74. /**
  75. * Specifies the width for thumbnails. Default is 1000. This value
  76. * must be between 220 and 1600.
  77. */
  78. DriveClient.prototype.thumbnailWidth = 1000;
  79. /**
  80. * The maximum number of bytes per thumbnail. Default is 2000000.
  81. */
  82. DriveClient.prototype.maxThumbnailSize = 2000000;
  83. /**
  84. * Defines the base64url PNG to be used if no thumbnail was generated
  85. * (including the case where thumbnails are disabled).
  86. */
  87. DriveClient.prototype.placeholderThumbnail = 'iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAMAAAAL34HQAAACN1BMVEXwhwXvhgX4iwXzhwXgbQzvhgXhbAzocgzqcwzldAoAAADhbgvjcQnmdgrlbgDwhgXsfwXufgjwhgXwgQfziAXxgADibgz4iwX4jAX3iwTpcwr1igXoewjsfgj3igX4iwXqcQv4jAX3iwXtfQnndQrvhAbibArwhwXgbQz//////v39jwX6jQX+/v7fagHfawzdVQDwhADgbhPgbhXwhwPocQ3uvKvwiA/faQDscgzxiAT97+XgciTgcSP6jAXgbQ3gcCHwiRfpcQzwhwfeXQD77ef74NLvhgTvegD66uPgbAf66+TvfADwjCzgcCfwiSD67ObhcjjwiBHhczvwiyrgbxj///777ujgcSHgcB/xiRzgbhveWgDeVwDhdEDgbRDqfgffYgDfXwD97+bvfQDxiz7//vvwiRr118rrcgztggbfZgDfZAD++PT98+3gbBPsgAb99vD33tPgcB7icAvuhAX//Pn66N/00sTyy7vuuqbjekLwhwzkcgr88er449n++vfutp/kh1vgcBvhbwvmdwnwgwDwgADeWQD87eLxxrTssJjqpIf0roHmjWTkhFP759n63czvvanomnjnlHDhczD22cr4y6/wwa/3xKX2wJ3rqpH0tY7qp4vpnoDymlbjf0vxjjntcwzldAroegj/kgX12s7518PzqnnnkWfynmLieUjpewjrdAD40Lj1uZTzpm3idTbiciLydQzzfwnyiQTsfgD3xqnzp3TxlkzgbCrdTwDdSwBLKUlNAAAAJ3RSTlP8/b2X/YH8wb+FAIuIggJbQin5opAM9+a/ubaubyD78NjSyr2WgRp4sjN4AAAI70lEQVR42u2cZ38SQRDGT8WGvfde4E4BxVMRRaKiUURRlJhQRDCCSgQVO/bee++9994+nMt5ywoezFJd/fm8uITi3p9n5mbYkcCpO6rVnVu2YEXd+3dRIySuo7pLv4GjGNKg7j3UHTl1l14PajmG9OFBnx7Ird4PumpYEtf1QXc112l0M7OGKXEfeg3guo3iNIyJG92Jaz61mYYxcaNacs1H/8f6j6X5j1WI/mMVIsawRFEzI49SjwOqAJa43emclk8Rp2c7AFZ+LDGyvXE2kmO2Q1Lq17RSd6ND48QIwFVuLNHTOPbEpTOz8ujMpccHGz0AV5mxIo4TpwUeUPj0YwfAVVYs0Tn7VZjnBUA8v+n6CyfERY8FR/DEJj7MQ6oL85vOvfDUAsuVC8s19s5yXuAppOPnvPk4EeSCsehCeBVTwVzHfE6RcFUQa4an8Qw91kpbw2oz4aoc1sSxniO0WAI/J24wriabmEpizZtM79bc+fr4/tUarEpiLabGElJYRsOGjbJfjGDpJCxtmosRLOEnVpqLESzZLYlLg65H1rAkLo2GESwcROwXI1jELcS1Y6OGQSzEVaupZQJLDiLhYtCtFBcbbslYhOueqKllDwtzwVhTq4RFuBh0C3EdEBl0C3OBWNUrEISLvSD+5GLQLYmLoSqfwcUiFuaqzhYDxiJc981lxqqdVsCGbHPcQLBgrtK3rwLt9tWqhblKxxI9hW3267U5ZHhuBrCKzXl4NIJTS5FrmbmMWGIEDZIouOp0/O6boYQ2jxBXWcdu13fzRILuF/2Ku+aGr96uBbhALHo5Z38+XcfXyVRZVx/+Ed513ldDCCCu0rFE0Xlo2mu5TAj8ki0XV0q6ePHilhi+d/15b9ACQGGusg3AFzc+XSMBCPzu89+CNlnB7zfD8t1z4iaLXUvDVT6sGdMOnv5pi47f6r9Qk9YF3xZ0l8S11UfMArlgLMpZM6bamYy6rWnta9q7TrZrzZPgPgoqg3atubY8WK6D8lQXHfb4p/wSK7vFfxmxSsAPQ96AlZ4LxoLNeompdkUDGQVznL5mLr4ar5ESD3PBWHA9fbpbjlT4pq1Bm6H6w9dwfOd69ePouNDYt3S3ULPGZ96S3YqtAW/Tepz1E8bgAANc+xEXhAX36ut1cslcd6rJq81SIvgEe7lmL3kY5iqxVYvOI9isswp22KeMOcrriJlWai5giwHl+yec73Ma9Mbfz+qOJndKz6hLpR5V1uPxavFuTTt0K1XfpbNeO0wKeUaR2IPBN5sMRlqu1eY8bsFmPeIFUpi0CjIGTLvSZY2EGeYSi3VL9Dgeb0I+SQl9MlcZT4TObZKzfmfS5NZSx1GsLQ5r+8Sxp7ERR/1TtDlUn2qNuGXCrZGM5URlLDiEVzDVkje5fdjXdDsm27XpXChBz4XG0UpYcDOMYaxjGc3wtyJxFtu1PohaI71f2K2imqEONcN4nrMZ9TWbMf81wg9z3VNwC26Gr3enY4ObobLqbccFefuz5AKONpVfzQp2y3NoVvrN32GLNl9orA22lTiM+Nqg5CJY1DueOjkwsdtNgAP7gidR2SWVhFqt3o9QwoKHIuiwDcwX+xT/UWztSlvCaqXGmtQBY1GadQmfh6anuE0XlkhhRFs3tGGkd+tuIVhiJN0M+brj0mlAu46lX0bcbizVLbgZrgwl4JhYA+NQa9TJQUetsSJYHscJvAVct7eJKoUbQudxPYmdirqzsYsIojhjoitD01yadH287J+vpZF1/uGt2K4ttinjshQo2C2XMzI2U64X6WY4tyZq99a7wZS3eA3BpNyrUPn1x00Z0uM1ACzilOfg7EN3VmRo8dN16WYYerYw6G9qCOSDCjQ0jQkufRbalt65LVyapaA/2mClxhK3Rxy3rsyavDxDR/DL5sMLFiyYu/7sXps7z8VldPv2Xl6PnjlTwOOuJQuytH7CXpvXCOQWoZrYeHWd4nw2Q+v22OLGnFSG0Nk1PCi0xjgjpVvTGi8hht9F+ARBGq8dtXmtOSLoDm1FhUSHnihkTecESalHkPAaWVhtFbA8jqvQGBmbt8fWkKtNn0Xw9GvAWK6DX9bBVHjzqtyvvcG9a+jXyC5oKoKV/a4YFG7Yij2ofszlgtaA3ZoRwW+pIOH3w0qZFURNh3oNtKsDsAr9LNvMC0pj93H6hTPpX9ocg8FIgTVvcgFYC03jFLBMi6ix0MDAoi8/lh7Cgt2q0VfNrSX0ayhjTa2IW0tKdotNrMq4NbPkILKZW+xdiSoGgshogfh7Ul7FcIEoFevfrPLC3+XWf6y/CEvHZoFQqlts9sQigqjLxFpQCJauakFcsqhKPXH79rGb6bE2B5Qmu0b91zn0WJtN8Wys9tgtIqfjEf2SWw7XKI8gHuKQ0X0eDsQSI44TaGBN6dYN5dlI/eFj9I7f8GWtoUJYOIgkiq6Ds/gw5T7dZDUqTrfscbLbB9eIB7JmEKsUgiii/4uO8ToBfJlhfif5tEGWEsGTMT4Mr6HDa0BBlP5Y88lcnkdkCtLhnyjMM0+Gcn2WzW6xnd/J8zn+LZq4SUeEvUBaA8LCs6Tk1p1AetXt3JoMWexWZSyr3RK6vSUGrRHbmkRUVgCLpP1HW/L4tgl5tO140mdKKFFhrkTUdxta4xleA8DCXC6n/vCYvPJFa9zAWL4m6qNaA8IiqjW73lreWnJrSj0AJYFZpvwq6RZRzjVUGEtB5tX7DdoqCXaL+PXHuEjdYsuvVqva4Sqv6NdabdW4YLeIKsoFYzHGhYPIGBd2izGuVpPaSVgAV7VEsOQgsuUXdosxLuwWxLVMW0WRK5ExLiiIpN4vq2YYVTiIbPmFgii5xRiXimCBqmIcVSS3WMqvdMqz5VcKqzdKeca4UrnVT/ryR6bi2Opuf64TwYJlfl4FLqu2Zxeux5BRXZnisvZ8103NqTtzoziuGa24+wZVRdVK9W7wyNSX1nYeOmrU6JSmjp6KhH5BR+kGvk++Ld0c/X66rPH4SEQeGl+kpq8a33eAumPqK347durWpzm9hrWhUevi1Hd4ZzVC+gGMHY0TYnDOYwAAAABJRU5ErkJggg=='.replace(/\+/g, '-').replace(/\//g, '_');
  88. /**
  89. * Mime type for the paceholder thumbnail.
  90. */
  91. DriveClient.prototype.placeholderMimeType = 'image/png';
  92. /**
  93. * Executes the first step for connecting to Google Drive.
  94. */
  95. DriveClient.prototype.libraryMimeType = 'application/vnd.jgraph.mxlibrary';
  96. /**
  97. * Contains the hostname of the new app.
  98. */
  99. DriveClient.prototype.newAppHostname = 'app.diagrams.net';
  100. /**
  101. * Executes the first step for connecting to Google Drive.
  102. */
  103. DriveClient.prototype.extension = '.drawio';
  104. /**
  105. * Interval for updating the access token.
  106. */
  107. DriveClient.prototype.tokenRefreshInterval = 0;
  108. /**
  109. * Interval for updating the access token.
  110. */
  111. DriveClient.prototype.lastTokenRefresh = 0;
  112. /**
  113. * Executes the first step for connecting to Google Drive.
  114. */
  115. DriveClient.prototype.maxRetries = 5;
  116. /**
  117. * Executes the first step for connecting to Google Drive.
  118. */
  119. DriveClient.prototype.staleEtagMaxRetries = 4;
  120. /**
  121. * Executes the first step for connecting to Google Drive.
  122. */
  123. DriveClient.prototype.coolOff = 1000;
  124. /**
  125. * Executes the first step for connecting to Google Drive.
  126. */
  127. DriveClient.prototype.mimeTypeCheckCoolOff = 60000;
  128. /**
  129. * Executes the first step for connecting to Google Drive.
  130. */
  131. DriveClient.prototype.user = null;
  132. /**
  133. * Executes auth in same window (no popups)
  134. */
  135. DriveClient.prototype.sameWinAuthMode = false;
  136. /**
  137. * Redirect URL of samw window mode that will get the token
  138. */
  139. DriveClient.prototype.sameWinRedirectUrl = null;
  140. /**
  141. * Authorizes the client, gets the userId and calls <open>.
  142. */
  143. DriveClient.prototype.setUser = function(user)
  144. {
  145. this.user = user;
  146. if (this.user == null)
  147. {
  148. this.userId = null;
  149. if (this.tokenRefreshThread != null)
  150. {
  151. window.clearTimeout(this.tokenRefreshThread);
  152. this.tokenRefreshThread = null;
  153. }
  154. }
  155. else
  156. {
  157. this.userId = user.id;
  158. }
  159. this.fireEvent(new mxEventObject('userChanged'));
  160. };
  161. DriveClient.prototype.setUserId = function(userId)
  162. {
  163. this.userId = userId;
  164. if (this.user != null && this.user.id != this.userId)
  165. {
  166. this.user = null;
  167. }
  168. };
  169. /**
  170. * Authorizes the client, gets the userId and calls <open>.
  171. */
  172. DriveClient.prototype.getUser = function()
  173. {
  174. return this.user;
  175. };
  176. DriveClient.prototype.getUsersList = function()
  177. {
  178. var users = [];
  179. var authInfo = JSON.parse(this.getPersistentToken(true));
  180. var curUserId = null;
  181. if (authInfo != null)
  182. {
  183. if (authInfo.current != null)
  184. {
  185. curUserId = authInfo.current.userId;
  186. users.push(authInfo[curUserId].user);
  187. users[0].isCurrent = true;
  188. }
  189. for (var id in authInfo)
  190. {
  191. if (id == 'current' || id == curUserId) continue;
  192. users.push(authInfo[id].user);
  193. }
  194. }
  195. return users;
  196. };
  197. DriveClient.prototype.logout = function()
  198. {
  199. //Send to server to clear refresh token cookie
  200. this.ui.editor.loadUrl(this.redirectUri + '?doLogout=1&userId=' + this.userId + '&state=' + encodeURIComponent('cId=' + this.clientId + '&domain=' + window.location.host));
  201. this.clearPersistentToken();
  202. this.setUser(null);
  203. _token = null;
  204. };
  205. /**
  206. * Authorizes the client, gets the userId and calls <open>.
  207. */
  208. DriveClient.prototype.execute = function(fn)
  209. {
  210. // Handles error in immediate authorize call via callback that shows a
  211. // UI with a button that executes the second non-immediate authorize
  212. var fallback = mxUtils.bind(this, function(resp)
  213. {
  214. // Remember is an argument for the callback that executes
  215. // when the user clicks the authorize button in the UI and
  216. // success executes after successful authorization.
  217. this.ui.showAuthDialog(this, true, mxUtils.bind(this, function(remember, success)
  218. {
  219. this.authorize(false, mxUtils.bind(this, function()
  220. {
  221. if (success != null)
  222. {
  223. success();
  224. }
  225. fn();
  226. }), mxUtils.bind(this, function(resp)
  227. {
  228. var msg = (resp.message != null) ? resp.message : mxResources.get('cannotLogin');
  229. // Handles special domain policy errors
  230. if (resp != null && resp.error != null)
  231. {
  232. if (resp.error.code == 403 &&
  233. resp.error.data != null && resp.error.data.length > 0 &&
  234. resp.error.data[0].reason == 'domainPolicy')
  235. {
  236. msg = resp.error.message;
  237. }
  238. }
  239. this.logout();
  240. this.ui.showError(mxResources.get('error'), msg, mxResources.get('help'), mxUtils.bind(this, function()
  241. {
  242. this.ui.openLink('https://www.drawio.com/doc/faq/gsuite-authorisation-troubleshoot');
  243. }), null, mxResources.get('ok'));
  244. }), remember);
  245. }));
  246. });
  247. // First immediate authorize attempt
  248. this.authorize(true, fn, fallback);
  249. };
  250. /**
  251. * Executes the given request.
  252. */
  253. DriveClient.prototype.executeRequest = function(reqObj, success, error)
  254. {
  255. try
  256. {
  257. var acceptResponse = true;
  258. var timeoutThread = null;
  259. var retryCount = 0;
  260. // Cancels any pending requests
  261. if (this.requestThread != null)
  262. {
  263. window.clearTimeout(this.requestThread);
  264. }
  265. var fn = mxUtils.bind(this, function()
  266. {
  267. try
  268. {
  269. this.requestThread = null;
  270. this.currentRequest = reqObj;
  271. if (timeoutThread != null)
  272. {
  273. window.clearTimeout(timeoutThread);
  274. }
  275. timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  276. {
  277. acceptResponse = false;
  278. if (error != null)
  279. {
  280. error({code: App.ERROR_TIMEOUT, message: mxResources.get('timeout'), retry: fn});
  281. }
  282. }), this.ui.timeout);
  283. var params = null;
  284. var isJSON = false;
  285. if (typeof reqObj.params === 'string')
  286. {
  287. params = reqObj.params;
  288. }
  289. else if (reqObj.params != null)
  290. {
  291. params = JSON.stringify(reqObj.params);
  292. isJSON = true;
  293. }
  294. var url = reqObj.fullUrl || (this.GDriveBaseUrl + reqObj.url);
  295. if (isJSON)
  296. {
  297. url += (url.indexOf('?') > 0 ? '&' : '?') + 'alt=json';
  298. }
  299. var req = new mxXmlRequest(url, params, reqObj.method || 'GET');
  300. req.setRequestHeaders = mxUtils.bind(this, function(request, params)
  301. {
  302. if (reqObj.headers != null)
  303. {
  304. for (var key in reqObj.headers)
  305. {
  306. request.setRequestHeader(key, reqObj.headers[key]);
  307. }
  308. }
  309. else if (reqObj.contentType != null)
  310. {
  311. request.setRequestHeader('Content-Type', reqObj.contentType);
  312. }
  313. else if (isJSON)
  314. {
  315. request.setRequestHeader('Content-Type', 'application/json');
  316. }
  317. request.setRequestHeader('Authorization', 'Bearer ' + _token);
  318. });
  319. req.send(mxUtils.bind(this, function(req)
  320. {
  321. try
  322. {
  323. window.clearTimeout(timeoutThread);
  324. if (acceptResponse)
  325. {
  326. var resp;
  327. try
  328. {
  329. resp = JSON.parse(req.getText());
  330. }
  331. catch(e)
  332. {
  333. resp = null;
  334. }
  335. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  336. {
  337. if (success != null)
  338. {
  339. success(resp);
  340. }
  341. }
  342. else
  343. {
  344. // Errors for put request are in data instead of errors
  345. var data = (resp != null && resp.error != null) ? ((resp.error.data != null) ?
  346. resp.error.data : resp.error.errors) : null;
  347. var reason = (data != null && data.length > 0) ? data[0].reason : null;
  348. // Handles special error for saving old file where mime was changed to new
  349. // LATER: Check if 403 is never auth error, for now we check the message for a specific
  350. // case where the old app mime type was overridden by the new app
  351. if (error != null && resp != null && resp.error != null && (resp.error.code == -1 ||
  352. (resp.error.code == 403 && (reason == 'domainPolicy' || resp.error.message ==
  353. 'The requested mime type change is forbidden.'))))
  354. {
  355. error(resp);
  356. }
  357. // Handles authentication error
  358. else if (resp != null && resp.error != null && (resp.error.code == 401 ||
  359. (resp.error.code == 403 && reason != 'rateLimitExceeded')))
  360. {
  361. // Shows an error if re-authenticated but the server still doesn't allow it
  362. if ((resp.error.code == 403 && this.retryAuth) ||
  363. (resp.error.code == 401 && this.retryAuth && reason == 'authError'))
  364. {
  365. if (error != null)
  366. {
  367. error(resp);
  368. }
  369. this.retryAuth = false;
  370. }
  371. else
  372. {
  373. this.retryAuth = true;
  374. this.execute(fn);
  375. }
  376. }
  377. // Schedules a retry if no new request was executed
  378. else if (resp != null && resp.error != null && resp.error.code != 412 && resp.error.code != 404 &&
  379. resp.error.code != 400 && this.currentRequest == reqObj && retryCount < this.maxRetries)
  380. {
  381. retryCount++;
  382. var jitter = 1 + 0.1 * (Math.random() - 0.5);
  383. this.requestThread = window.setTimeout(fn,
  384. Math.round(Math.pow(2, retryCount) *
  385. jitter * this.coolOff));
  386. }
  387. else if (error != null)
  388. {
  389. error(resp);
  390. }
  391. }
  392. }
  393. }
  394. catch (e)
  395. {
  396. if (error != null)
  397. {
  398. error(e);
  399. }
  400. else
  401. {
  402. throw e;
  403. }
  404. }
  405. }));
  406. }
  407. catch (e)
  408. {
  409. if (error != null)
  410. {
  411. error(e);
  412. }
  413. else
  414. {
  415. throw e;
  416. }
  417. }
  418. });
  419. // Must get token before first request in this case
  420. if (_token == null || !this.authCalled)
  421. {
  422. this.execute(fn);
  423. }
  424. else
  425. {
  426. fn();
  427. }
  428. }
  429. catch (e)
  430. {
  431. if (error != null)
  432. {
  433. error(e);
  434. }
  435. else
  436. {
  437. throw e;
  438. }
  439. }
  440. };
  441. /**
  442. * Executes the given request.
  443. */
  444. DriveClient.prototype.createAuthWin = function(url)
  445. {
  446. var width = 525,
  447. height = 525,
  448. screenX = window.screenX,
  449. screenY = window.screenY,
  450. outerWidth = window.outerWidth,
  451. outerHeight = window.outerHeight;
  452. var left = screenX + Math.max(outerWidth - width, 0) / 2;
  453. var top = screenY + Math.max(outerHeight - height, 0) / 2;
  454. var features = ['width=' + width, 'height=' + height,
  455. 'top=' + top, 'left=' + left,
  456. 'status=no', 'resizable=yes',
  457. 'toolbar=no', 'menubar=no',
  458. 'scrollbars=yes'];
  459. return window.open(url? url : 'about:blank', 'gdauth', features.join(','));
  460. };
  461. /**
  462. * Authorizes the client, gets the userId and calls <open>.
  463. */
  464. DriveClient.prototype.authorize = function(immediate, success, error, remember, popup)
  465. {
  466. if (this.isExtAuth && !immediate)
  467. {
  468. window.parent.driveAuth(mxUtils.bind(this, function(newAuthInfo)
  469. {
  470. this.updateAuthInfo(newAuthInfo, true, true, success, error);
  471. }), error);
  472. return;
  473. }
  474. var req = new mxXmlRequest(this.redirectUri + '?getState=1', null, 'GET');
  475. req.send(mxUtils.bind(this, function(req)
  476. {
  477. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  478. {
  479. this.authorizeStep2(req.getText(), immediate, success, error, remember, popup);
  480. }
  481. else if (error != null)
  482. {
  483. error(req);
  484. }
  485. }), error);
  486. };
  487. /**
  488. * Executes the given request.
  489. */
  490. DriveClient.prototype.updateAuthInfo = function (newAuthInfo, remember, forceUserUpdate, success, error)
  491. {
  492. _token = newAuthInfo.access_token;
  493. delete newAuthInfo.access_token; //Don't store access token
  494. newAuthInfo.expires = Date.now() + parseInt(newAuthInfo.expires_in) * 1000;
  495. newAuthInfo.remember = remember;
  496. this.resetTokenRefresh(newAuthInfo);
  497. this.authCalled = true;
  498. if (forceUserUpdate || this.user == null)
  499. {
  500. //IE/Edge security doesn't allow access to newAuthInfo in a callback function (outside this function scope)
  501. //So, stringify the object and restore it (parse) in the callback
  502. var strAuthInfo = JSON.stringify(newAuthInfo);
  503. this.updateUser(mxUtils.bind(this, function()
  504. {
  505. //Restore the auth info object to bypass IE/Edge security
  506. var resAuthInfo = JSON.parse(strAuthInfo);
  507. //Save user and new token
  508. this.setPersistentToken(resAuthInfo, !remember);
  509. if (success != null)
  510. {
  511. success();
  512. }
  513. }), error);
  514. }
  515. else if (success != null)
  516. {
  517. this.setPersistentToken(newAuthInfo, !remember);
  518. success();
  519. }
  520. };
  521. /**
  522. * Executes the given request.
  523. */
  524. DriveClient.prototype.authorizeStep2 = function(state, immediate, success, error, remember, popup)
  525. {
  526. try
  527. {
  528. // Takes userId from state URL parameter
  529. if (this.ui.stateArg != null && this.ui.stateArg.userId != null)
  530. {
  531. this.userId = this.ui.stateArg.userId;
  532. if (this.user != null && this.user.id != this.userId)
  533. {
  534. this.user = null;
  535. }
  536. }
  537. if (this.userId == null)
  538. {
  539. var authInfo = JSON.parse(this.getPersistentToken(true));
  540. if (authInfo && authInfo.current != null)
  541. {
  542. this.userId = authInfo.current.userId;
  543. }
  544. }
  545. // Immediate only possible with a refresh token (there is a userId)
  546. if (immediate && this.userId == null)
  547. {
  548. if (error != null)
  549. {
  550. error();
  551. }
  552. }
  553. else
  554. {
  555. //Retry request with refreshed token (in the cookie)
  556. if (immediate) //Note, we checked refresh token is not null above
  557. {
  558. //state is used to identify which app/domain is used
  559. var req = new mxXmlRequest(this.redirectUri + '?state=' + encodeURIComponent('cId=' + this.clientId +
  560. '&domain=' + window.location.host + '&token=' + state) + '&userId=' + this.userId, null, 'GET');
  561. req.send(mxUtils.bind(this, function(req)
  562. {
  563. try
  564. {
  565. if (req.getStatus() >= 200 && req.getStatus() <= 299)
  566. {
  567. var newAuthInfo = JSON.parse(req.getText());
  568. this.updateAuthInfo(newAuthInfo, true, false, success, error); //We set remember to true since we can only have a refresh token if user initially selected remember
  569. }
  570. else
  571. {
  572. //When the request fails (e.g, Hibernate on Windows), the status is 0, this doesn't mean the token is invalid
  573. if (req.getStatus() != 0)
  574. {
  575. this.logout();
  576. }
  577. if (error != null)
  578. {
  579. error(req); //TODO review this code path and how error is handled
  580. }
  581. }
  582. }
  583. catch (e)
  584. {
  585. if (window.console != null)
  586. {
  587. console.log('DriveClient.authorizeStep2', e);
  588. }
  589. if (error != null)
  590. {
  591. error(e);
  592. }
  593. else
  594. {
  595. throw e;
  596. }
  597. }
  598. }), error);
  599. }
  600. else
  601. {
  602. var url = 'https://accounts.google.com/o/oauth2/v2/auth?client_id=' + this.clientId +
  603. '&redirect_uri=' + encodeURIComponent(this.redirectUri) +
  604. '&response_type=code&include_granted_scopes=true' +
  605. (remember? '&access_type=offline&prompt=consent%20select_account' : '') + //Ask for consent again to get a new refresh token
  606. '&scope=' + encodeURIComponent(this.scopes.join(' ')) +
  607. '&state=' + encodeURIComponent('cId=' + this.clientId + '&domain=' + window.location.host + '&token=' + state + //To identify which app/domain is used
  608. (this.sameWinRedirectUrl? '&redirect=' + this.sameWinRedirectUrl : ''));
  609. if (this.sameWinAuthMode)
  610. {
  611. window.location.assign(url);
  612. popup = null; //Same window doesn't use onGoogleDriveCallback or popups
  613. }
  614. else if (popup == null)
  615. {
  616. popup = this.createAuthWin(url);
  617. }
  618. else
  619. {
  620. popup.location = url;
  621. }
  622. if (popup != null)
  623. {
  624. window.onGoogleDriveCallback = mxUtils.bind(this, function(newAuthInfo, authWindow)
  625. {
  626. window.onGoogleDriveCallback = null;
  627. try
  628. {
  629. if (newAuthInfo == null)
  630. {
  631. if (error != null)
  632. {
  633. error({message: mxResources.get('accessDenied')}); //TODO Check this error handling is correct
  634. }
  635. }
  636. else
  637. {
  638. this.updateAuthInfo(newAuthInfo, remember, true, success, error);
  639. }
  640. }
  641. catch (e)
  642. {
  643. if (error != null)
  644. {
  645. error(e);
  646. }
  647. }
  648. finally
  649. {
  650. if (authWindow != null)
  651. {
  652. authWindow.close();
  653. }
  654. }
  655. });
  656. popup.focus();
  657. }
  658. else if (error != null)
  659. {
  660. error({message: mxResources.get('allowPopups')});
  661. }
  662. }
  663. }
  664. }
  665. catch (e)
  666. {
  667. if (error != null)
  668. {
  669. error(e);
  670. }
  671. else
  672. {
  673. throw e;
  674. }
  675. }
  676. };
  677. /**
  678. * Checks if the client is authorized and calls the next step.
  679. */
  680. DriveClient.prototype.resetTokenRefresh = function(resp)
  681. {
  682. if (this.tokenRefreshThread != null)
  683. {
  684. window.clearTimeout(this.tokenRefreshThread);
  685. this.tokenRefreshThread = null;
  686. }
  687. // Starts timer to refresh token before it expires
  688. if (resp != null && resp.error == null && resp.expires_in > 0)
  689. {
  690. this.tokenRefreshInterval = parseInt(resp.expires_in) * 1000;
  691. this.lastTokenRefresh = new Date().getTime();
  692. this.tokenRefreshThread = window.setTimeout(mxUtils.bind(this, function()
  693. {
  694. this.authorize(true, mxUtils.bind(this, function()
  695. {
  696. //console.log('tokenRefresh: refreshed', _token);
  697. }), mxUtils.bind(this, function()
  698. {
  699. //console.log('tokenRefresh: error refreshing', _token);
  700. }));
  701. }), resp.expires_in * 900);
  702. }
  703. };
  704. /**
  705. * Checks if the client is authorized and calls the next step.
  706. */
  707. DriveClient.prototype.checkToken = function(fn)
  708. {
  709. var connected = this.lastTokenRefresh > 0;
  710. var delta = new Date().getTime() - this.lastTokenRefresh;
  711. if (delta > this.tokenRefreshInterval || this.tokenRefreshThread == null)
  712. {
  713. // Uses execute instead of authorize to allow a fallback authorization if cookie was lost
  714. this.execute(mxUtils.bind(this, function()
  715. {
  716. fn();
  717. if (connected)
  718. {
  719. this.fireEvent(new mxEventObject('disconnected'));
  720. }
  721. }));
  722. }
  723. else
  724. {
  725. fn();
  726. }
  727. };
  728. /**
  729. * Checks if the client is authorized and calls the next step.
  730. */
  731. DriveClient.prototype.updateUser = function(success, error)
  732. {
  733. try
  734. {
  735. var url = 'https://www.googleapis.com/oauth2/v2/userinfo?alt=json';
  736. var headers = {'Authorization': 'Bearer ' + _token};
  737. this.ui.editor.loadUrl(url, mxUtils.bind(this, function(data)
  738. {
  739. try
  740. {
  741. var info = JSON.parse(data);
  742. // Requests more information about the user (email address is sometimes not in info)
  743. this.executeRequest({url: '/about'}, mxUtils.bind(this, function(resp)
  744. {
  745. var email = mxResources.get('notAvailable');
  746. var name = email;
  747. var pic = null;
  748. if (resp != null && resp.user != null)
  749. {
  750. email = resp.user.emailAddress;
  751. name = resp.user.displayName;
  752. pic = (resp.user.picture != null) ? resp.user.picture.url : null;
  753. }
  754. this.setUser(new DrawioUser(info.id, email, name, pic, info.locale));
  755. this.userId = info.id;
  756. if (success != null)
  757. {
  758. success();
  759. }
  760. }), error);
  761. }
  762. catch (e)
  763. {
  764. if (error != null)
  765. {
  766. error(e);
  767. }
  768. }
  769. }), error, null, null, null, null, headers);
  770. }
  771. catch (e)
  772. {
  773. if (error != null)
  774. {
  775. error(e);
  776. }
  777. else
  778. {
  779. throw e;
  780. }
  781. }
  782. };
  783. /**
  784. * Translates this point by the given vector.
  785. *
  786. * @param {number} dx X-coordinate of the translation.
  787. * @param {number} dy Y-coordinate of the translation.
  788. */
  789. DriveClient.prototype.copyFile = function(id, title, success, error)
  790. {
  791. if (id != null && title != null)
  792. {
  793. this.executeRequest({url: '/files/' + id + '/copy?fields=' + encodeURIComponent(this.allFields)
  794. + '&supportsAllDrives=true&enforceSingleParent=true', //&alt=json
  795. method: 'POST',
  796. params: {'title': title, 'properties':
  797. [{'key': 'channel', 'value': Editor.guid()}]}
  798. }, success, error);
  799. }
  800. };
  801. /**
  802. * Translates this point by the given vector.
  803. *
  804. * @param {number} dx X-coordinate of the translation.
  805. * @param {number} dy Y-coordinate of the translation.
  806. */
  807. DriveClient.prototype.renameFile = function(id, title, success, error)
  808. {
  809. if (id != null && title != null)
  810. {
  811. this.executeRequest(this.createDriveRequest(
  812. id, {'title' : title}), success, error);
  813. }
  814. };
  815. /**
  816. * Translates this point by the given vector.
  817. *
  818. * @param {number} dx X-coordinate of the translation.
  819. * @param {number} dy Y-coordinate of the translation.
  820. */
  821. DriveClient.prototype.moveFile = function(id, folderId, success, error)
  822. {
  823. if (id != null && folderId != null)
  824. {
  825. this.executeRequest(this.createDriveRequest(id, {'parents': [{'kind':
  826. 'drive#fileLink', 'id': folderId}]}), success, error);
  827. }
  828. };
  829. /**
  830. * Translates this point by the given vector.
  831. *
  832. * @param {number} dx X-coordinate of the translation.
  833. * @param {number} dy Y-coordinate of the translation.
  834. */
  835. DriveClient.prototype.createDriveRequest = function(id, body)
  836. {
  837. return {
  838. 'url': '/files/' + id + '?uploadType=multipart&supportsAllDrives=true',
  839. 'method': 'PUT',
  840. 'contentType': 'application/json; charset=UTF-8',
  841. 'params': body
  842. };
  843. };
  844. /**
  845. * Loads the given file as a library file.
  846. */
  847. DriveClient.prototype.getLibrary = function(id, success, error)
  848. {
  849. return this.getFile(id, success, error, true, true);
  850. };
  851. /**
  852. * Loads the descriptorf for the given file ID.
  853. */
  854. DriveClient.prototype.loadDescriptor = function(id, success, error, fields)
  855. {
  856. this.executeRequest({
  857. url: '/files/' + id + '?supportsAllDrives=true&fields=' + (fields != null ? fields : this.allFields)
  858. }, success, error);
  859. };
  860. DriveClient.prototype.listFiles = function(searchStr, afterDate, mineOnly, success, error)
  861. {
  862. this.executeRequest({
  863. url: '/files?supportsAllDrives=true&includeItemsFromAllDrives=true&q=' + encodeURIComponent('(mimeType contains \'' + this.xmlMimeType + '\') ' +
  864. (searchStr? ' and (title contains \'' + searchStr + '\')' : '') +
  865. (afterDate? ' and (modifiedDate > \'' + afterDate.toISOString() + '\')' : '') +
  866. (mineOnly? ' and (\'me\' in owners)' : '')) +
  867. '&orderBy=modifiedDate desc,title'
  868. }, success, error);
  869. };
  870. /**
  871. * Gets the channel ID from the given descriptor.
  872. */
  873. DriveClient.prototype.getCustomProperty = function(desc, key)
  874. {
  875. var props = desc.properties;
  876. var result = null;
  877. if (props != null)
  878. {
  879. for (var i = 0; i < props.length; i++)
  880. {
  881. if (props[i].key == key)
  882. {
  883. result = props[i].value;
  884. break;
  885. }
  886. }
  887. }
  888. return result;
  889. };
  890. /**
  891. * Checks if the client is authorized and calls the next step. The optional
  892. * readXml argument is used for import. Default is false. The optional
  893. * readLibrary argument is used for reading libraries. Default is false.
  894. */
  895. DriveClient.prototype.getFile = function(id, success, error, readXml, readLibrary)
  896. {
  897. readXml = (readXml != null) ? readXml : false;
  898. readLibrary = (readLibrary != null) ? readLibrary : false;
  899. if (urlParams['rev'] != null)
  900. {
  901. this.executeRequest({
  902. url: '/files/' + id + '/revisions/' + urlParams['rev'] + '?supportsAllDrives=true'
  903. },
  904. mxUtils.bind(this, function(resp)
  905. {
  906. // Redirects title to originalFilename to
  907. // match expected descriptor interface
  908. resp.title = resp.originalFilename;
  909. // Uses ID of file instead of revision ID in descriptor
  910. // to avoid a change of the document hash property
  911. resp.headRevisionId = resp.id;
  912. resp.id = id;
  913. this.getXmlFile(resp, success, error);
  914. }), error);
  915. }
  916. else
  917. {
  918. this.loadDescriptor(id, mxUtils.bind(this, function(resp)
  919. {
  920. try
  921. {
  922. if (this.user != null)
  923. {
  924. var binary = /\.png$/i.test(resp.title);
  925. // Handles .vsdx, .vsd, .vdx, Gliffy and PNG+XML files by creating a temporary file
  926. if (/\.v(dx|sdx?)$/i.test(resp.title) || /\.gliffy$/i.test(resp.title) ||
  927. (!this.ui.useCanvasForExport && binary))
  928. {
  929. var url = resp.downloadUrl;
  930. var headers = {'Authorization': 'Bearer ' + _token};
  931. this.ui.convertFile(url, resp.title, resp.mimeType, this.extension, success, error, null, headers);
  932. }
  933. else
  934. {
  935. // Handles converted realtime files as XML files
  936. if (readXml || readLibrary || resp.mimeType == this.libraryMimeType ||
  937. resp.mimeType == this.xmlMimeType)
  938. {
  939. this.getXmlFile(resp, success, error, true, readLibrary);
  940. }
  941. else
  942. {
  943. this.getXmlFile(resp, success, error);
  944. }
  945. }
  946. }
  947. else
  948. {
  949. error({message: mxResources.get('loggedOut')});
  950. }
  951. }
  952. catch (e)
  953. {
  954. if (error != null)
  955. {
  956. error(e);
  957. }
  958. else
  959. {
  960. throw e;
  961. }
  962. }
  963. }), error);
  964. }
  965. };
  966. /**
  967. * Returns true if the given mime type is for Google Realtime files.
  968. */
  969. DriveClient.prototype.isGoogleRealtimeMimeType = function(mimeType)
  970. {
  971. return mimeType != null && mimeType.substring(0, 30) == 'application/vnd.jgraph.mxfile.';
  972. };
  973. /**
  974. * Checks if the client is authorized and calls the next step. The ignoreMime argument is
  975. * used for import via getFile. Default is false. The optional
  976. * readLibrary argument is used for reading libraries. Default is false.
  977. */
  978. DriveClient.prototype.getXmlFile = function(resp, success, error, ignoreMime, readLibrary)
  979. {
  980. try
  981. {
  982. var headers = {'Authorization': 'Bearer ' + _token};
  983. var url = resp.downloadUrl;
  984. // Download URL is null if no option to download for viewers
  985. if (url == null)
  986. {
  987. if (error != null)
  988. {
  989. error({message: mxResources.get('exportOptionsDisabledDetails')});
  990. }
  991. }
  992. else
  993. {
  994. var retryCount = 0;
  995. var fn = mxUtils.bind(this, function()
  996. {
  997. // Loads XML to initialize realtime document if realtime is empty
  998. this.ui.editor.loadUrl(url, mxUtils.bind(this, function(data)
  999. {
  1000. try
  1001. {
  1002. if (data == null)
  1003. {
  1004. // TODO: Optional redirect to legacy if link is for old file
  1005. error({message: mxResources.get('invalidOrMissingFile')});
  1006. }
  1007. else if (resp.mimeType == this.libraryMimeType || readLibrary)
  1008. {
  1009. if (resp.mimeType == this.libraryMimeType && !readLibrary)
  1010. {
  1011. error({message: mxResources.get('notADiagramFile')});
  1012. }
  1013. else
  1014. {
  1015. success(new DriveLibrary(this.ui, data, resp));
  1016. }
  1017. }
  1018. else
  1019. {
  1020. var importFile = false;
  1021. if (/\.png$/i.test(resp.title))
  1022. {
  1023. var index = data.lastIndexOf(',');
  1024. if (index > 0)
  1025. {
  1026. var xml = this.ui.extractGraphModelFromPng(data);
  1027. if (xml != null && xml.length > 0)
  1028. {
  1029. data = xml;
  1030. }
  1031. else
  1032. {
  1033. // Checks if the file contains XML data which can happen when we insert
  1034. // the file and then don't post-process it when loaded into the UI which
  1035. // is required for creating the images for .PNG and .SVG files.
  1036. try
  1037. {
  1038. var xml = data.substring(index + 1);
  1039. var temp = (window.atob && !mxClient.IS_IE && !mxClient.IS_IE11) ?
  1040. atob(xml) : Base64.decode(xml);
  1041. var node = this.ui.editor.extractGraphModel(
  1042. mxUtils.parseXml(temp).documentElement, true);
  1043. if (node == null || node.getElementsByTagName('parsererror').length > 0)
  1044. {
  1045. importFile = true;
  1046. }
  1047. else
  1048. {
  1049. data = temp;
  1050. }
  1051. }
  1052. catch (e)
  1053. {
  1054. importFile = true;
  1055. }
  1056. }
  1057. }
  1058. }
  1059. else if (/\.pdf$/i.test(resp.title))
  1060. {
  1061. var xml = Editor.extractGraphModelFromPdf(data);
  1062. if (xml != null && xml.length > 0)
  1063. {
  1064. importFile = true;
  1065. data = xml;
  1066. }
  1067. }
  1068. // Checks for base64 encoded mxfile
  1069. else if (data.substring(0, 32) == 'data:image/png;base64,PG14ZmlsZS')
  1070. {
  1071. var temp = data.substring(22);
  1072. data = (window.atob && !mxClient.IS_SF) ? atob(temp) : Base64.decode(temp);
  1073. }
  1074. if (Graph.fileSupport && new XMLHttpRequest().upload && this.ui.isRemoteFileFormat(data, url))
  1075. {
  1076. this.ui.parseFileData(data, mxUtils.bind(this, function(xhr)
  1077. {
  1078. try
  1079. {
  1080. if (xhr.readyState == 4)
  1081. {
  1082. if (xhr.status >= 200 && xhr.status <= 299)
  1083. {
  1084. success(new LocalFile(this.ui, xhr.responseText, resp.title + this.extension, true));
  1085. }
  1086. else if (error != null)
  1087. {
  1088. error({message: mxResources.get('errorLoadingFile')});
  1089. }
  1090. }
  1091. }
  1092. catch (e)
  1093. {
  1094. if (error != null)
  1095. {
  1096. error(e);
  1097. }
  1098. else
  1099. {
  1100. throw e;
  1101. }
  1102. }
  1103. }), resp.title);
  1104. }
  1105. else
  1106. {
  1107. success((importFile) ? new LocalFile(this.ui, data, resp.title, true) : new DriveFile(this.ui, data, resp));
  1108. }
  1109. }
  1110. }
  1111. catch (e)
  1112. {
  1113. if (error != null)
  1114. {
  1115. error(e);
  1116. }
  1117. else
  1118. {
  1119. throw e;
  1120. }
  1121. }
  1122. }), mxUtils.bind(this, function(e, req)
  1123. {
  1124. if (retryCount < this.maxRetries && req != null && req.getStatus() == 403)
  1125. {
  1126. retryCount++;
  1127. var jitter = 1 + 0.1 * (Math.random() - 0.5);
  1128. var delay = retryCount * 2 * this.coolOff * jitter;
  1129. window.setTimeout(fn, delay);
  1130. }
  1131. else
  1132. {
  1133. if (error != null)
  1134. {
  1135. error(e);
  1136. }
  1137. else
  1138. {
  1139. throw e;
  1140. }
  1141. }
  1142. }), ((resp.mimeType != null && resp.mimeType.substring(0, 6) == 'image/' &&
  1143. resp.mimeType.substring(0, 9) != 'image/svg')) || /\.png$/i.test(resp.title) ||
  1144. /\.jpe?g$/i.test(resp.title) || /\.pdf$/i.test(resp.title),
  1145. null, null, null, headers);
  1146. });
  1147. fn();
  1148. }
  1149. }
  1150. catch (e)
  1151. {
  1152. if (error != null)
  1153. {
  1154. error(e);
  1155. }
  1156. else
  1157. {
  1158. throw e;
  1159. }
  1160. }
  1161. };
  1162. /**
  1163. * Translates this point by the given vector.
  1164. *
  1165. * @param {number} dx X-coordinate of the translation.
  1166. * @param {number} dy Y-coordinate of the translation.
  1167. */
  1168. DriveClient.prototype.saveFile = function(file, revision, success, errFn, noCheck, unloading, overwrite, properties, secret)
  1169. {
  1170. try
  1171. {
  1172. var retryCount = 0;
  1173. file.saveLevel = 1;
  1174. var error = mxUtils.bind(this, function(e)
  1175. {
  1176. if (errFn != null)
  1177. {
  1178. errFn(e);
  1179. }
  1180. else
  1181. {
  1182. throw e;
  1183. }
  1184. // Logs failed save
  1185. // try
  1186. // {
  1187. // if (!file.isConflict(e))
  1188. // {
  1189. // var err = 'sl_' + file.saveLevel + '-error_' +
  1190. // (file.getErrorMessage(e) || 'unknown');
  1191. // if (e != null && e.error != null && e.error.code != null)
  1192. // {
  1193. // err += '-code_' + e.error.code;
  1194. // }
  1195. // EditorUi.logEvent({category: 'ERROR-SAVE-FILE-' + file.getHash() + '-rev_' +
  1196. // file.desc.headRevisionId + '-mod_' + file.desc.modifiedDate +
  1197. // '-size_' + file.getSize() + '-mime_' + file.desc.mimeType +
  1198. // ((this.ui.editor.autosave) ? '' : '-nosave') +
  1199. // ((file.isAutosave()) ? '' : '-noauto') +
  1200. // ((file.changeListenerEnabled) ? '' : '-nolisten') +
  1201. // ((file.inConflictState) ? '-conflict' : '') +
  1202. // ((file.invalidChecksum) ? '-invalid' : ''),
  1203. // action: err, label: ((this.user != null) ? ('user_' + this.user.id) : 'nouser') +
  1204. // ((file.sync != null) ? ('-client_' + file.sync.clientId) : '-nosync')});
  1205. // }
  1206. // }
  1207. // catch (ex)
  1208. // {
  1209. // // ignore
  1210. // }
  1211. });
  1212. var criticalError = mxUtils.bind(this, function(e)
  1213. {
  1214. error(e);
  1215. try
  1216. {
  1217. EditorUi.logError(e.message, null, null, null, e);
  1218. // EditorUi.sendReport('Critical error in DriveClient.saveFile ' +
  1219. // new Date().toISOString() + ':' +
  1220. // '\n\nUserAgent=' + navigator.userAgent +
  1221. // '\nAppVersion=' + navigator.appVersion +
  1222. // '\nAppName=' + navigator.appName +
  1223. // '\nPlatform=' + navigator.platform +
  1224. // '\nFile=' + file.desc.id + '.' + file.desc.headRevisionId +
  1225. // '\nMime=' + file.desc.mimeType +
  1226. // '\nSize=' + file.getSize() +
  1227. // '\nUser=' + ((this.user != null) ? this.user.id : 'nouser') +
  1228. // ((file.sync != null) ? '-client_' + file.sync.clientId : '-nosync') +
  1229. // '\nSaveLevel=' + file.saveLevel +
  1230. // '\nSaveAsPng=' + (this.ui.useCanvasForExport && /(\.png)$/i.test(file.getTitle())) +
  1231. // '\nRetryCount=' + retryCount +
  1232. // '\nError=' + e +
  1233. // '\nMessage=' + e.message +
  1234. // '\n\nStack:\n' + e.stack);
  1235. }
  1236. catch (e)
  1237. {
  1238. // ignore
  1239. }
  1240. });
  1241. if (file.isEditable() && file.desc != null)
  1242. {
  1243. var t0 = new Date().getTime();
  1244. var etag0 = file.desc.etag;
  1245. var mod0 = file.desc.modifiedDate;
  1246. var head0 = file.desc.headRevisionId;
  1247. var saveAsPng = this.ui.useCanvasForExport && /(\.png)$/i.test(file.getTitle());
  1248. noCheck = (noCheck != null) ? noCheck : urlParams['ignoremime'] == '1';
  1249. // NOTE: Unloading arg is currently ignored, saving during unload/beforeUnload is not possible using
  1250. // asynchronous code, which is needed to create the thumbnail, or asynchronous requests which is the only
  1251. // way to execute the gapi request below.
  1252. // If no thumbnail is created and noCheck is true (which is always true if unloading is true) in which case
  1253. // this code is synchronous, the executeRequest call is reached but the request is still not sent. This is
  1254. // true for both, calls from beforeUnload and unload handlers. Note sure how to make the call synchronous
  1255. // which is said to fix this when called from beforeUnload.
  1256. // However, this would result in a missing thumbnail in most cases so a better solution might be to reduce
  1257. // the autosave interval in DriveRealtime, but that would increase the number of requests.
  1258. unloading = (unloading != null) ? unloading : false;
  1259. var prevDesc = null;
  1260. var pinned = false;
  1261. var meta =
  1262. {
  1263. 'mimeType': file.desc.mimeType,
  1264. 'title': file.getTitle()
  1265. };
  1266. // Overrides old mime type and creates a revision
  1267. if (this.isGoogleRealtimeMimeType(meta.mimeType))
  1268. {
  1269. meta.mimeType = this.xmlMimeType;
  1270. prevDesc = file.desc;
  1271. revision = true;
  1272. pinned = true;
  1273. }
  1274. // Overrides mime type for unknown file type uploads
  1275. else if (meta.mimeType == 'application/octet-stream' ||
  1276. (urlParams['override-mime'] == '1' &&
  1277. meta.mimeType != this.xmlMimeType))
  1278. {
  1279. meta.mimeType = this.xmlMimeType;
  1280. }
  1281. // Adds optional thumbnail to upload request
  1282. var doSave = mxUtils.bind(this, function(thumb, thumbMime, keepExisting)
  1283. {
  1284. try
  1285. {
  1286. file.saveLevel = 3;
  1287. var savedData = file.getData();
  1288. var checksum = null;
  1289. var pages = null;
  1290. if (file.constructor == DriveFile)
  1291. {
  1292. if (properties == null)
  1293. {
  1294. properties = [];
  1295. }
  1296. // Channel ID appended to file ID for comms
  1297. if (file.getChannelId() == null)
  1298. {
  1299. properties.push({'key': 'channel', 'value': Editor.guid(32)});
  1300. }
  1301. // Key for encryption of comms
  1302. if (file.getChannelKey() == null)
  1303. {
  1304. properties.push({'key': 'key', 'value': Editor.guid(32)});
  1305. }
  1306. // Pass to access cache for each etag
  1307. secret = (secret != null) ? secret : Editor.guid(32);
  1308. properties.push({'key': 'secret', 'value': secret});
  1309. pages = this.ui.getPagesForXml(savedData)
  1310. checksum = this.ui.getHashValueForPages(pages);
  1311. // Writes checksum with secret to file properties
  1312. // The secret is required to check if the checksum is updated
  1313. // with the last write as old clients keep existing entries
  1314. properties.push({'key': 'checksum', 'value': secret + ':' + checksum});
  1315. }
  1316. // Specifies that no thumbnail should be uploaded in which case the existing thumbnail is used
  1317. if (!keepExisting)
  1318. {
  1319. // Uses placeholder thumbnail to replace existing one except when unloading
  1320. // in which case the XML is updated but the existing thumbnail is not in order
  1321. // to avoid executing asynchronous code and get the XML to the server instead
  1322. if (thumb == null && !unloading)
  1323. {
  1324. thumb = this.placeholderThumbnail;
  1325. thumbMime = this.placeholderMimeType;
  1326. }
  1327. // Adds metadata for thumbnail
  1328. if (thumb != null && thumbMime != null)
  1329. {
  1330. meta.thumbnail =
  1331. {
  1332. 'image': thumb,
  1333. 'mimeType': thumbMime
  1334. };
  1335. }
  1336. }
  1337. // Updates saveDelay on drive file
  1338. var wrapper = mxUtils.bind(this, function(resp)
  1339. {
  1340. try
  1341. {
  1342. file.saveDelay = new Date().getTime() - t0;
  1343. file.saveLevel = null;
  1344. success(resp, savedData, pages, checksum);
  1345. if (prevDesc != null)
  1346. {
  1347. // Pins previous revision
  1348. this.executeRequest({
  1349. url: '/files/' + prevDesc.id + '/revisions/' + prevDesc.headRevisionId + '?supportsAllDrives=true'
  1350. }, mxUtils.bind(this, mxUtils.bind(this, function(resp)
  1351. {
  1352. resp.pinned = true;
  1353. this.executeRequest({
  1354. url: '/files/' + prevDesc.id + '/revisions/' + prevDesc.headRevisionId,
  1355. method: 'PUT',
  1356. params: resp
  1357. });
  1358. })));
  1359. // Logs conversion
  1360. try
  1361. {
  1362. EditorUi.logEvent({category: file.convertedFrom + '-CONVERT-FILE-' + file.getHash(),
  1363. action: 'from_' + prevDesc.id + '.' + prevDesc.headRevisionId +
  1364. '-to_' + file.desc.id + '.' + file.desc.headRevisionId,
  1365. label: (this.user != null) ? ('user_' + this.user.id) : 'nouser' +
  1366. ((file.sync != null) ? '-client_' + file.sync.clientId : 'nosync')});
  1367. }
  1368. catch (e)
  1369. {
  1370. // ignore
  1371. }
  1372. }
  1373. // Logs successful save
  1374. // try
  1375. // {
  1376. // EditorUi.logEvent({category: 'SUCCESS-SAVE-FILE-' + file.getHash() +
  1377. // '-rev0_' + head0 + '-mod0_' + mod0,
  1378. // action: 'rev-' + resp.headRevisionId +
  1379. // '-mod_' + resp.modifiedDate + '-size_' + file.getSize() +
  1380. // '-mime_' + file.desc.mimeType +
  1381. // ((this.ui.editor.autosave) ? '' : '-nosave') +
  1382. // ((file.isAutosave()) ? '' : '-noauto') +
  1383. // ((file.changeListenerEnabled) ? '' : '-nolisten') +
  1384. // ((file.inConflictState) ? '-conflict' : '') +
  1385. // ((file.invalidChecksum) ? '-invalid' : ''),
  1386. // label: ((this.user != null) ? ('user_' + this.user.id) : 'nouser') +
  1387. // ((file.sync != null) ? ('-client_' + file.sync.clientId) : '-nosync')});
  1388. // }
  1389. // catch (e)
  1390. // {
  1391. // // ignore
  1392. // }
  1393. }
  1394. catch (e)
  1395. {
  1396. criticalError(e);
  1397. }
  1398. });
  1399. var doExecuteRequest = mxUtils.bind(this, function(data, binary)
  1400. {
  1401. file.saveLevel = 4;
  1402. try
  1403. {
  1404. if (properties != null)
  1405. {
  1406. meta.properties = properties;
  1407. }
  1408. // Used to check if file was changed externally
  1409. var etag = (!overwrite && file.constructor == DriveFile &&
  1410. (DrawioFile.SYNC == 'manual' || DrawioFile.SYNC == 'auto')) ?
  1411. file.getCurrentEtag() : null;
  1412. var doExecuteSave = mxUtils.bind(this, function(realOverwrite)
  1413. {
  1414. file.saveLevel = 5;
  1415. try
  1416. {
  1417. var unknown = file.desc.mimeType != this.xmlMimeType && file.desc.mimeType != this.mimeType &&
  1418. file.desc.mimeType != this.libraryMimeType;
  1419. var acceptResponse = true;
  1420. var timeoutThread = null;
  1421. // Allow for re-auth flow with 5x timeout
  1422. try
  1423. {
  1424. timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  1425. {
  1426. acceptResponse = false;
  1427. error({code: App.ERROR_TIMEOUT});
  1428. }), 5 * this.ui.timeout);
  1429. }
  1430. catch (e)
  1431. {
  1432. // Ignore window closed
  1433. }
  1434. this.executeRequest(this.createUploadRequest(file.getId(), meta,
  1435. data, revision || realOverwrite || unknown, binary,
  1436. (realOverwrite) ? null : etag, pinned), mxUtils.bind(this, function(resp)
  1437. {
  1438. window.clearTimeout(timeoutThread);
  1439. if (acceptResponse)
  1440. {
  1441. // Checks if modified time is in the future and etag and head revision have changed
  1442. var delta = (resp != null) ? new Date(resp.modifiedDate).getTime() - new Date(mod0).getTime() : 0;
  1443. if (delta <= 0 || etag0 == resp.etag || (revision && head0 == resp.headRevisionId))
  1444. {
  1445. var reasons = [];
  1446. if (resp == null)
  1447. {
  1448. reasons.push('Empty response');
  1449. }
  1450. else
  1451. {
  1452. if (delta <= 0)
  1453. {
  1454. reasons.push('Invalid modified time');
  1455. }
  1456. if (etag0 == resp.etag)
  1457. {
  1458. reasons.push('Stale etag');
  1459. }
  1460. if (revision && head0 == resp.headRevisionId)
  1461. {
  1462. reasons.push('Stale revision');
  1463. }
  1464. // Updates etag for retry
  1465. etag = resp.etag;
  1466. }
  1467. if (reasons.length == 0)
  1468. {
  1469. reasons.push(mxResources.get('unknownError'));
  1470. }
  1471. var temp = reasons.join(', ');
  1472. if (retryCount < this.staleEtagMaxRetries)
  1473. {
  1474. retryCount++;
  1475. var jitter = 1 + 0.1 * (Math.random() - 0.5);
  1476. var delay = Math.round(Math.pow(2, retryCount) * this.coolOff * jitter);
  1477. window.setTimeout(doExecuteSave, delay);
  1478. if (urlParams['test'] == '1')
  1479. {
  1480. EditorUi.debug('DriveClient: Invalid response',
  1481. 'retry', retryCount, 'delay', delay, 'rev',
  1482. (resp != null) ? resp.headRevisionId : 'null',
  1483. 'errors', temp);
  1484. }
  1485. }
  1486. else
  1487. {
  1488. file.saveLevel = 12;
  1489. error({message: mxResources.get('error') + ': ' + temp}, resp);
  1490. // Logs failed save
  1491. try
  1492. {
  1493. EditorUi.logError('Saving to Google Drive failed',
  1494. null, 'id-' + file.desc.id +
  1495. '-from-' + head0 + '.' + mod0 + '-' + this.ui.hashValue(etag0) +
  1496. '-to-' + resp.headRevisionId + '.' + resp.modifiedDate + '-' +
  1497. this.ui.hashValue(resp.etag) + ((temp.length > 0) ? '-errors-' + temp : ''),
  1498. 'user-' + ((this.user != null) ? this.user.id : 'nouser') +
  1499. ((file.sync != null) ? '-client_' + file.sync.clientId : '-nosync') +
  1500. '-retries-' + retryCount);
  1501. }
  1502. catch (e)
  1503. {
  1504. // ignore
  1505. }
  1506. }
  1507. }
  1508. else
  1509. {
  1510. wrapper(resp);
  1511. }
  1512. }
  1513. }), mxUtils.bind(this, function(err)
  1514. {
  1515. window.clearTimeout(timeoutThread);
  1516. if (acceptResponse)
  1517. {
  1518. file.saveLevel = 6;
  1519. try
  1520. {
  1521. if (!file.isConflict(err))
  1522. {
  1523. error(err);
  1524. }
  1525. else
  1526. {
  1527. // Workaround for correct etag and Google always returns 412 conflict error (stale etag)
  1528. this.executeRequest({
  1529. url: '/files/' + file.getId() + '?supportsAllDrives=true&fields=' + this.catchupFields
  1530. },
  1531. mxUtils.bind(this, function(resp)
  1532. {
  1533. file.saveLevel = 7;
  1534. try
  1535. {
  1536. // Stale etag detected, retry with delay
  1537. if (resp != null && resp.etag == etag)
  1538. {
  1539. if (retryCount < this.staleEtagMaxRetries)
  1540. {
  1541. retryCount++;
  1542. var jitter = 1 + 0.1 * (Math.random() - 0.5);
  1543. var delay = Math.round(retryCount * 2 * this.coolOff * jitter);
  1544. window.setTimeout(executeSave, delay);
  1545. if (urlParams['test'] == '1')
  1546. {
  1547. EditorUi.debug('DriveClient: Stale Etag Detected',
  1548. 'retry', retryCount, 'delay', delay);
  1549. }
  1550. }
  1551. else
  1552. {
  1553. executeSave(true);
  1554. // Logs overwrite
  1555. try
  1556. {
  1557. EditorUi.logEvent({category: 'STALE-ETAG-SAVE-FILE-' + file.getHash(),
  1558. action: 'rev_' + file.desc.headRevisionId + '-mod_' + file.desc.modifiedDate +
  1559. '-size_' + file.getSize() + '-mime_' + file.desc.mimeType +
  1560. ((this.ui.editor.autosave) ? '' : '-nosave') +
  1561. ((file.isAutosave()) ? '' : '-noauto') +
  1562. ((file.changeListenerEnabled) ? '' : '-nolisten') +
  1563. ((file.inConflictState) ? '-conflict' : '') +
  1564. ((file.invalidChecksum) ? '-invalid' : ''),
  1565. label: ((this.user != null) ? ('user_' + this.user.id) : 'nouser') +
  1566. ((file.sync != null) ? ('-client_' + file.sync.clientId) : '-nosync')});
  1567. }
  1568. catch (e)
  1569. {
  1570. // ignore
  1571. }
  1572. }
  1573. }
  1574. else
  1575. {
  1576. if (urlParams['test'] == '1' && resp.headRevisionId == head0)
  1577. {
  1578. EditorUi.debug('DriveClient: Remote Etag Changed',
  1579. 'local', etag, 'remote', resp.etag,
  1580. 'rev', file.desc.headRevisionId,
  1581. 'response', [resp], 'file', [file]);
  1582. }
  1583. error(err, resp);
  1584. }
  1585. }
  1586. catch (e)
  1587. {
  1588. criticalError(e);
  1589. }
  1590. }), mxUtils.bind(this, function()
  1591. {
  1592. error(err);
  1593. }));
  1594. }
  1595. }
  1596. catch (e)
  1597. {
  1598. criticalError(e);
  1599. }
  1600. }
  1601. }));
  1602. }
  1603. catch (e)
  1604. {
  1605. criticalError(e);
  1606. }
  1607. });
  1608. // Workaround for Google returning the wrong etag after file save is to
  1609. // update the etag before save and check if the headRevisionId changed
  1610. var executeSave = mxUtils.bind(this, function(realOverwrite)
  1611. {
  1612. file.saveLevel = 9;
  1613. if (realOverwrite || etag == null)
  1614. {
  1615. doExecuteSave(realOverwrite);
  1616. }
  1617. else
  1618. {
  1619. var acceptResponse = true;
  1620. var timeoutThread = null;
  1621. // Allow for re-auth flow with 3x timeout
  1622. try
  1623. {
  1624. timeoutThread = window.setTimeout(mxUtils.bind(this, function()
  1625. {
  1626. acceptResponse = false;
  1627. error({code: App.ERROR_TIMEOUT});
  1628. }), 3 * this.ui.timeout);
  1629. }
  1630. catch (e)
  1631. {
  1632. // Ignore window closed
  1633. }
  1634. this.executeRequest({
  1635. url: '/files/' + file.getId() + '?supportsAllDrives=true&fields=' + this.catchupFields
  1636. },
  1637. mxUtils.bind(this, function(desc2)
  1638. {
  1639. window.clearTimeout(timeoutThread);
  1640. if (acceptResponse)
  1641. {
  1642. file.saveLevel = 10;
  1643. try
  1644. {
  1645. // Checks head revision ID and updates etag or returns conflict
  1646. if (desc2 != null && desc2.headRevisionId == head0)
  1647. {
  1648. if (urlParams['test'] == '1' && etag != desc2.etag)
  1649. {
  1650. EditorUi.debug('DriveClient: Preflight Etag Update',
  1651. 'from', etag, 'to', desc2.etag,
  1652. 'rev', file.desc.headRevisionId,
  1653. 'response', [desc2], 'file', [file]);
  1654. }
  1655. etag = desc2.etag;
  1656. doExecuteSave(realOverwrite);
  1657. }
  1658. else
  1659. {
  1660. error({error: {code: 412}}, desc2);
  1661. }
  1662. }
  1663. catch (e)
  1664. {
  1665. criticalError(e);
  1666. }
  1667. }
  1668. }), mxUtils.bind(this, function(err)
  1669. {
  1670. // Simulated
  1671. window.clearTimeout(timeoutThread);
  1672. if (acceptResponse)
  1673. {
  1674. file.saveLevel = 11;
  1675. error(err);
  1676. }
  1677. }));
  1678. }
  1679. });
  1680. // Uses saved PNG data for thumbnail
  1681. if (saveAsPng && thumb == null)
  1682. {
  1683. file.saveLevel = 8;
  1684. var img = new Image();
  1685. img.onload = mxUtils.bind(this, function()
  1686. {
  1687. try
  1688. {
  1689. var s = this.thumbnailWidth / img.width;
  1690. var canvas = document.createElement('canvas');
  1691. canvas.width = this.thumbnailWidth;
  1692. canvas.height = Math.floor(img.height * s);
  1693. var ctx = canvas.getContext('2d');
  1694. ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
  1695. var temp = canvas.toDataURL();
  1696. temp = temp.substring(temp.indexOf(',') + 1).replace(/\+/g, '-').replace(/\//g, '_');
  1697. meta.thumbnail =
  1698. {
  1699. 'image': temp,
  1700. 'mimeType': 'image/png'
  1701. };
  1702. executeSave(false);
  1703. }
  1704. catch (e)
  1705. {
  1706. try
  1707. {
  1708. executeSave(false)
  1709. }
  1710. catch (e2)
  1711. {
  1712. criticalError(e2);
  1713. }
  1714. }
  1715. });
  1716. img.src = 'data:image/png;base64,' + data;
  1717. }
  1718. else
  1719. {
  1720. executeSave(false);
  1721. }
  1722. }
  1723. catch (e)
  1724. {
  1725. criticalError(e);
  1726. }
  1727. });
  1728. if (saveAsPng)
  1729. {
  1730. var p = this.ui.getPngFileProperties(this.ui.fileNode);
  1731. this.ui.getEmbeddedPng(mxUtils.bind(this, function(data)
  1732. {
  1733. doExecuteRequest(data, true);
  1734. }), error, (this.ui.getCurrentFile() != file) ?
  1735. savedData : null, p.scale, p.border);
  1736. }
  1737. else
  1738. {
  1739. doExecuteRequest(savedData, false);
  1740. }
  1741. }
  1742. catch (e)
  1743. {
  1744. criticalError(e);
  1745. }
  1746. });
  1747. // Indirection to generate thumbnails if enabled and supported
  1748. // (required because generation of thumbnails is asynchronous)
  1749. try
  1750. {
  1751. file.saveLevel = 2;
  1752. // NOTE: getThumbnail is asynchronous and returns false if no thumbnails can be created
  1753. if (unloading || saveAsPng || file.constructor == DriveLibrary || !this.enableThumbnails || urlParams['thumb'] == '0' ||
  1754. (meta.mimeType != null && meta.mimeType.substring(0, 29) != 'application/vnd.jgraph.mxfile') ||
  1755. !this.ui.getThumbnail(this.thumbnailWidth, mxUtils.bind(this, function(canvas)
  1756. {
  1757. // Callback for getThumbnail
  1758. try
  1759. {
  1760. var thumb = null;
  1761. try
  1762. {
  1763. if (canvas != null)
  1764. {
  1765. // Security errors are possible
  1766. thumb = canvas.toDataURL('image/png');
  1767. }
  1768. // Maximum thumbnail size is 2MB
  1769. if (thumb != null)
  1770. {
  1771. if (thumb.length > this.maxThumbnailSize)
  1772. {
  1773. thumb = null;
  1774. }
  1775. else
  1776. {
  1777. // Converts base64 data into required format for Drive (base64url with no prefix)
  1778. thumb = thumb.substring(thumb.indexOf(',') + 1).replace(/\+/g, '-').replace(/\//g, '_');
  1779. }
  1780. }
  1781. }
  1782. catch (e)
  1783. {
  1784. thumb = null;
  1785. }
  1786. doSave(thumb, 'image/png');
  1787. }
  1788. catch (e)
  1789. {
  1790. criticalError(e);
  1791. }
  1792. }), 20))
  1793. {
  1794. // If-branch
  1795. doSave(null, null, file.constructor != DriveLibrary);
  1796. }
  1797. }
  1798. catch (e)
  1799. {
  1800. criticalError(e);
  1801. }
  1802. }
  1803. else
  1804. {
  1805. this.ui.editor.graph.reset();
  1806. error({message: mxResources.get('readOnly')});
  1807. }
  1808. }
  1809. catch (e)
  1810. {
  1811. criticalError(e);
  1812. }
  1813. };
  1814. /**
  1815. * Translates this point by the given vector.
  1816. *
  1817. * @param {number} dx X-coordinate of the translation.
  1818. * @param {number} dy Y-coordinate of the translation.
  1819. */
  1820. DriveClient.prototype.insertFile = function(title, data, folderId, success, error, mimeType, binary)
  1821. {
  1822. mimeType = (mimeType != null) ? mimeType : this.xmlMimeType;
  1823. var metadata =
  1824. {
  1825. 'mimeType': mimeType,
  1826. 'title': title
  1827. };
  1828. if (folderId != null)
  1829. {
  1830. metadata.parents = [{'kind': 'drive#fileLink', 'id': folderId}];
  1831. }
  1832. // NOTE: Cannot create thumbnail on insert since no ui has no current file
  1833. this.executeRequest(this.createUploadRequest(null, metadata, data, false, binary), mxUtils.bind(this, function(resp)
  1834. {
  1835. if (mimeType == this.libraryMimeType)
  1836. {
  1837. success(new DriveLibrary(this.ui, data, resp));
  1838. }
  1839. else if (resp == false)
  1840. {
  1841. if (error != null)
  1842. {
  1843. error({message: mxResources.get('errorSavingFile')});
  1844. }
  1845. }
  1846. else
  1847. {
  1848. success(new DriveFile(this.ui, data, resp));
  1849. }
  1850. }), error);
  1851. };
  1852. /**
  1853. * Translates this point by the given vector.
  1854. *
  1855. * @param {number} dx X-coordinate of the translation.
  1856. * @param {number} dy Y-coordinate of the translation.
  1857. */
  1858. DriveClient.prototype.createUploadRequest = function(id, metadata, data, revision, binary, etag, pinned)
  1859. {
  1860. binary = (binary != null) ? binary : false;
  1861. var bd = '-------314159265358979323846';
  1862. var delim = '\r\n--' + bd + '\r\n';
  1863. var close = '\r\n--' + bd + '--';
  1864. var ctype = 'application/octect-stream';
  1865. var headers = {'Content-Type' : 'multipart/mixed; boundary="' + bd + '"'};
  1866. if (etag != null)
  1867. {
  1868. headers['If-Match'] = etag;
  1869. }
  1870. var reqObj =
  1871. {
  1872. 'fullUrl': 'https://content.googleapis.com/upload/drive/v2/files' + (id != null ? '/' + id : '') +
  1873. '?uploadType=multipart&supportsAllDrives=true&enforceSingleParent=true&fields=' + this.allFields,
  1874. 'method': (id != null) ? 'PUT' : 'POST',
  1875. 'headers': headers,
  1876. 'params': delim + 'Content-Type: application/json\r\n\r\n' + JSON.stringify(metadata) + delim +
  1877. 'Content-Type: ' + ctype + '\r\n' + 'Content-Transfer-Encoding: base64\r\n' + '\r\n' +
  1878. ((data != null) ? ((binary) ? data : ((window.btoa && !mxClient.IS_IE && !mxClient.IS_IE11) ?
  1879. Graph.base64EncodeUnicode(data) : Base64.encode(data))) : '') + close
  1880. }
  1881. if (!revision)
  1882. {
  1883. reqObj.fullUrl += '&newRevision=false';
  1884. }
  1885. if (pinned)
  1886. {
  1887. reqObj.fullUrl += '&pinned=true';
  1888. }
  1889. return reqObj;
  1890. };
  1891. /**
  1892. * Translates this point by the given vector.
  1893. *
  1894. * @param {number} dx X-coordinate of the translation.
  1895. * @param {number} dy Y-coordinate of the translation.
  1896. */
  1897. DriveClient.prototype.createLinkPicker = function()
  1898. {
  1899. var name = 'linkPicker';
  1900. var picker = pickers[name];
  1901. if (picker == null || pickers[name + 'Token'] != _token)
  1902. {
  1903. pickers[name + 'Token'] = _token;
  1904. var view = new google.picker.DocsView(google.picker.ViewId.FOLDERS)
  1905. .setParent('root')
  1906. .setIncludeFolders(true)
  1907. .setSelectFolderEnabled(true);
  1908. var view2 = new google.picker.DocsView()
  1909. .setIncludeFolders(true)
  1910. .setSelectFolderEnabled(true);
  1911. var view21 = new google.picker.DocsView()
  1912. .setIncludeFolders(true)
  1913. .setEnableDrives(true)
  1914. .setSelectFolderEnabled(true);
  1915. picker = new google.picker.PickerBuilder()
  1916. .setAppId(this.appId)
  1917. .setLocale(mxLanguage)
  1918. .setOAuthToken(pickers[name + 'Token'])
  1919. .enableFeature(google.picker.Feature.SUPPORT_DRIVES)
  1920. .addView(view)
  1921. .addView(view2)
  1922. .addView(view21)
  1923. .addView(google.picker.ViewId.RECENTLY_PICKED);
  1924. }
  1925. return picker;
  1926. };
  1927. /**
  1928. * Translates this point by the given vector.
  1929. *
  1930. * @param {number} dx X-coordinate of the translation.
  1931. * @param {number} dy Y-coordinate of the translation.
  1932. */
  1933. DriveClient.prototype.pickFile = function(fn, acceptAllFiles, cancelFn)
  1934. {
  1935. this.filePickerCallback = (fn != null) ? fn : mxUtils.bind(this, function(id)
  1936. {
  1937. this.ui.loadFile('G' + id);
  1938. });
  1939. this.filePicked = mxUtils.bind(this, function(data)
  1940. {
  1941. if (data.action == google.picker.Action.PICKED)
  1942. {
  1943. this.filePickerCallback(data.docs[0].id, data.docs[0]);
  1944. }
  1945. });
  1946. if (this.ui.spinner.spin(document.body, mxResources.get('authorizing')))
  1947. {
  1948. this.execute(mxUtils.bind(this, function()
  1949. {
  1950. try
  1951. {
  1952. this.ui.spinner.stop();
  1953. // Reuses picker as long as token doesn't change.
  1954. var name = (acceptAllFiles) ? 'genericPicker' : 'filePicker';
  1955. // Click on background closes dialog as workaround for blocking dialog
  1956. // states such as 401 where the dialog cannot be closed and blocks UI
  1957. var exit = mxUtils.bind(this, function(evt)
  1958. {
  1959. // Workaround for click from appIcon on second call
  1960. if (mxEvent.getSource(evt).className == 'picker modal-dialog-bg picker-dialog-bg')
  1961. {
  1962. mxEvent.removeListener(document, 'click', exit);
  1963. this[name].setVisible(false);
  1964. if (cancelFn)
  1965. {
  1966. cancelFn();
  1967. }
  1968. }
  1969. });
  1970. if (pickers[name] == null || pickers[name + 'Token'] != _token)
  1971. {
  1972. // FIXME: Dispose not working
  1973. // if (pickers[name] != null)
  1974. // {
  1975. // console.log(name, pickers[name]);
  1976. // pickers[name].dispose();
  1977. // }
  1978. pickers[name + 'Token'] = _token;
  1979. // Pseudo-hierarchical directory view, see
  1980. // https://groups.google.com/forum/#!topic/google-picker-api/FSFcuJe7icQ
  1981. var view = new google.picker.DocsView(google.picker.ViewId.FOLDERS)
  1982. .setParent('root')
  1983. .setIncludeFolders(true);
  1984. var view2 = new google.picker.DocsView()
  1985. .setIncludeFolders(true);
  1986. var view3 = new google.picker.DocsView()
  1987. .setEnableDrives(true)
  1988. .setIncludeFolders(true);
  1989. var view4 = new google.picker.DocsUploadView()
  1990. .setIncludeFolders(true);
  1991. if (!acceptAllFiles)
  1992. {
  1993. view.setMimeTypes(this.mimeTypes);
  1994. view2.setMimeTypes(this.mimeTypes);
  1995. view3.setMimeTypes(this.mimeTypes);
  1996. }
  1997. else
  1998. {
  1999. view.setMimeTypes('*/*');
  2000. view2.setMimeTypes('*/*');
  2001. view3.setMimeTypes('*/*');
  2002. }
  2003. pickers[name] = new google.picker.PickerBuilder()
  2004. .setOAuthToken(pickers[name + 'Token'])
  2005. .setLocale(mxLanguage)
  2006. .setAppId(this.appId)
  2007. .enableFeature(google.picker.Feature.SUPPORT_DRIVES)
  2008. .addView(view)
  2009. .addView(view2)
  2010. .addView(view3)
  2011. .addView(google.picker.ViewId.RECENTLY_PICKED)
  2012. .addView(view4);
  2013. if (urlParams['gPickerSize'])
  2014. {
  2015. var cSize = urlParams['gPickerSize'].split(',');
  2016. pickers[name] = pickers[name].setSize(cSize[0], cSize[1]);
  2017. }
  2018. if (urlParams['topBaseUrl'])
  2019. {
  2020. pickers[name] = pickers[name].setOrigin(decodeURIComponent(urlParams['topBaseUrl']));
  2021. }
  2022. pickers[name] = pickers[name].setCallback(mxUtils.bind(this, function(data)
  2023. {
  2024. if (data.action == google.picker.Action.PICKED ||
  2025. data.action == google.picker.Action.CANCEL)
  2026. {
  2027. mxEvent.removeListener(document, 'click', exit);
  2028. if (cancelFn && data.action == google.picker.Action.CANCEL)
  2029. {
  2030. cancelFn();
  2031. }
  2032. }
  2033. if (data.action == google.picker.Action.PICKED)
  2034. {
  2035. this.filePicked(data);
  2036. }
  2037. })).build();
  2038. }
  2039. mxEvent.addListener(document, 'click', exit);
  2040. pickers[name].setVisible(true);
  2041. }
  2042. catch (e)
  2043. {
  2044. this.ui.spinner.stop();
  2045. this.ui.handleError(e);
  2046. }
  2047. }));
  2048. }
  2049. };
  2050. /**
  2051. * Translates this point by the given vector.
  2052. *
  2053. * @param {number} dx X-coordinate of the translation.
  2054. * @param {number} dy Y-coordinate of the translation.
  2055. */
  2056. DriveClient.prototype.pickFolder = function(fn, force)
  2057. {
  2058. this.folderPickerCallback = fn;
  2059. // Picker is initialized once and points to this function
  2060. // which is overridden each time to the picker is shown
  2061. var showPicker = mxUtils.bind(this, function()
  2062. {
  2063. try
  2064. {
  2065. if (this.ui.spinner.spin(document.body, mxResources.get('authorizing')))
  2066. {
  2067. this.execute(mxUtils.bind(this, function()
  2068. {
  2069. try
  2070. {
  2071. this.ui.spinner.stop();
  2072. // Reuses picker as long as token doesn't change.
  2073. var name = 'folderPicker';
  2074. // Click on background closes dialog as workaround for blocking dialog
  2075. // states such as 401 where the dialog cannot be closed and blocks UI
  2076. var exit = mxUtils.bind(this, function(evt)
  2077. {
  2078. // Workaround for click from appIcon on second call
  2079. if (mxEvent.getSource(evt).className == 'picker modal-dialog-bg picker-dialog-bg')
  2080. {
  2081. mxEvent.removeListener(document, 'click', exit);
  2082. pickers[name].setVisible(false);
  2083. }
  2084. });
  2085. if (pickers[name] == null || pickers[name + 'Token'] != _token)
  2086. {
  2087. // FIXME: Dispose not working
  2088. // if (pickers[name] != null)
  2089. // {
  2090. // console.log(name, pickers[name]);
  2091. // pickers[name].dispose();
  2092. // }
  2093. pickers[name + 'Token'] = _token;
  2094. // Pseudo-hierarchical directory view, see
  2095. // https://groups.google.com/forum/#!topic/google-picker-api/FSFcuJe7icQ
  2096. var view = new google.picker.DocsView(google.picker.ViewId.FOLDERS)
  2097. .setParent('root')
  2098. .setIncludeFolders(true)
  2099. .setSelectFolderEnabled(true)
  2100. .setMimeTypes('application/vnd.google-apps.folder');
  2101. var view2 = new google.picker.DocsView()
  2102. .setIncludeFolders(true)
  2103. .setSelectFolderEnabled(true)
  2104. .setMimeTypes('application/vnd.google-apps.folder');
  2105. var view3 = new google.picker.DocsView()
  2106. .setIncludeFolders(true)
  2107. .setEnableDrives(true)
  2108. .setSelectFolderEnabled(true)
  2109. .setMimeTypes('application/vnd.google-apps.folder');
  2110. pickers[name] = new google.picker.PickerBuilder()
  2111. .setSelectableMimeTypes('application/vnd.google-apps.folder')
  2112. .setOAuthToken(pickers[name + 'Token'])
  2113. .setLocale(mxLanguage)
  2114. .setAppId(this.appId)
  2115. .enableFeature(google.picker.Feature.SUPPORT_DRIVES)
  2116. .addView(view)
  2117. .addView(view2)
  2118. .addView(view3)
  2119. .addView(google.picker.ViewId.RECENTLY_PICKED)
  2120. .setTitle(mxResources.get('pickFolder'));
  2121. if (urlParams['gPickerSize'])
  2122. {
  2123. var cSize = urlParams['gPickerSize'].split(',');
  2124. pickers[name] = pickers[name].setSize(cSize[0], cSize[1]);
  2125. }
  2126. if (urlParams['topBaseUrl'])
  2127. {
  2128. pickers[name] = pickers[name].setOrigin(decodeURIComponent(urlParams['topBaseUrl']));
  2129. }
  2130. pickers[name] = pickers[name].setCallback(mxUtils.bind(this, function(data)
  2131. {
  2132. if (data.action == google.picker.Action.PICKED ||
  2133. data.action == google.picker.Action.CANCEL)
  2134. {
  2135. mxEvent.removeListener(document, 'click', exit);
  2136. }
  2137. this.folderPickerCallback(data);
  2138. })).build();
  2139. }
  2140. mxEvent.addListener(document, 'click', exit);
  2141. pickers[name].setVisible(true);
  2142. }
  2143. catch (e)
  2144. {
  2145. this.ui.spinner.stop();
  2146. this.ui.handleError(e);
  2147. }
  2148. }));
  2149. }
  2150. }
  2151. catch (e)
  2152. {
  2153. this.ui.handleError(e);
  2154. }
  2155. });
  2156. if (force)
  2157. {
  2158. showPicker();
  2159. }
  2160. else if (this.ui.spinner.spin(document.body, mxResources.get('authorizing')))
  2161. {
  2162. this.execute(mxUtils.bind(this, function()
  2163. {
  2164. try
  2165. {
  2166. this.ui.spinner.stop();
  2167. this.ui.confirm(mxResources.get('useRootFolder'), mxUtils.bind(this, function()
  2168. {
  2169. this.folderPickerCallback({action: google.picker.Action.PICKED,
  2170. docs: [{type: 'folder', id: 'root'}]});
  2171. }), mxUtils.bind(this, function()
  2172. {
  2173. showPicker();
  2174. }), mxResources.get('yes'), mxResources.get('noPickFolder') + '...', true);
  2175. }
  2176. catch (e)
  2177. {
  2178. this.ui.handleError(e);
  2179. }
  2180. }));
  2181. }
  2182. };
  2183. /**
  2184. * Translates this point by the given vector.
  2185. *
  2186. * @param {number} dx X-coordinate of the translation.
  2187. * @param {number} dy Y-coordinate of the translation.
  2188. */
  2189. DriveClient.prototype.pickLibrary = function(fn)
  2190. {
  2191. this.filePickerCallback = fn;
  2192. this.filePicked = mxUtils.bind(this, function(data)
  2193. {
  2194. if (data.action == google.picker.Action.PICKED)
  2195. {
  2196. this.filePickerCallback(data.docs[0].id);
  2197. }
  2198. else if (data.action == google.picker.Action.CANCEL && this.ui.getCurrentFile() == null)
  2199. {
  2200. this.ui.showSplash();
  2201. }
  2202. });
  2203. if (this.ui.spinner.spin(document.body, mxResources.get('authorizing')))
  2204. {
  2205. this.execute(mxUtils.bind(this, function()
  2206. {
  2207. try
  2208. {
  2209. this.ui.spinner.stop();
  2210. // Click on background closes dialog as workaround for blocking dialog
  2211. // states such as 401 where the dialog cannot be closed and blocks UI
  2212. var exit = mxUtils.bind(this, function(evt)
  2213. {
  2214. // Workaround for click from appIcon on second call
  2215. if (mxEvent.getSource(evt).className == 'picker modal-dialog-bg picker-dialog-bg')
  2216. {
  2217. mxEvent.removeListener(document, 'click', exit);
  2218. pickers.libraryPicker.setVisible(false);
  2219. }
  2220. });
  2221. // Reuses picker as long as token doesn't change
  2222. if (pickers.libraryPicker == null || pickers.libraryPickerToken != _token)
  2223. {
  2224. // FIXME: Dispose not working
  2225. // if (pickers[name] != null)
  2226. // {
  2227. // console.log(name, pickers[name]);
  2228. // pickers[name].dispose();
  2229. // }
  2230. pickers.libraryPickerToken = _token;
  2231. // Pseudo-hierarchical directory view, see
  2232. // https://groups.google.com/forum/#!topic/google-picker-api/FSFcuJe7icQ
  2233. var view = new google.picker.DocsView(google.picker.ViewId.FOLDERS)
  2234. .setParent('root')
  2235. .setIncludeFolders(true)
  2236. .setMimeTypes(this.libraryMimeType + ',application/xml,text/plain,application/octet-stream');
  2237. var view2 = new google.picker.DocsView()
  2238. .setIncludeFolders(true)
  2239. .setMimeTypes(this.libraryMimeType + ',application/xml,text/plain,application/octet-stream');
  2240. var view3 = new google.picker.DocsView()
  2241. .setEnableDrives(true)
  2242. .setIncludeFolders(true)
  2243. .setMimeTypes(this.libraryMimeType + ',application/xml,text/plain,application/octet-stream');
  2244. var view4 = new google.picker.DocsUploadView()
  2245. .setIncludeFolders(true);
  2246. pickers.libraryPicker = new google.picker.PickerBuilder()
  2247. .setOAuthToken(pickers.libraryPickerToken)
  2248. .setLocale(mxLanguage)
  2249. .setAppId(this.appId)
  2250. .enableFeature(google.picker.Feature.SUPPORT_DRIVES)
  2251. .addView(view)
  2252. .addView(view2)
  2253. .addView(view3)
  2254. .addView(google.picker.ViewId.RECENTLY_PICKED)
  2255. .addView(view4);
  2256. if (urlParams['gPickerSize'])
  2257. {
  2258. var cSize = urlParams['gPickerSize'].split(',');
  2259. pickers.libraryPicker = pickers.libraryPicker.setSize(cSize[0], cSize[1]);
  2260. }
  2261. if (urlParams['topBaseUrl'])
  2262. {
  2263. pickers.libraryPicker = pickers.libraryPicker.setOrigin(decodeURIComponent(urlParams['topBaseUrl']));
  2264. }
  2265. pickers.libraryPicker = pickers.libraryPicker.setCallback(mxUtils.bind(this, function(data)
  2266. {
  2267. if (data.action == google.picker.Action.PICKED ||
  2268. data.action == google.picker.Action.CANCEL)
  2269. {
  2270. mxEvent.removeListener(document, 'click', exit);
  2271. }
  2272. if (data.action == google.picker.Action.PICKED)
  2273. {
  2274. this.filePicked(data);
  2275. }
  2276. })).build();
  2277. }
  2278. mxEvent.addListener(document, 'click', exit);
  2279. pickers.libraryPicker.setVisible(true);
  2280. }
  2281. catch (e)
  2282. {
  2283. this.ui.spinner.stop();
  2284. this.ui.handleError(e);
  2285. }
  2286. }));
  2287. }
  2288. };
  2289. /**
  2290. * Translates this point by the given vector.
  2291. *
  2292. * @param {number} dx X-coordinate of the translation.
  2293. * @param {number} dy Y-coordinate of the translation.
  2294. */
  2295. DriveClient.prototype.showPermissions = function(id, file)
  2296. {
  2297. var fallback = mxUtils.bind(this, function()
  2298. {
  2299. var dlg = new ConfirmDialog(this.ui, mxResources.get('googleSharingNotAvailable'), mxUtils.bind(this, function()
  2300. {
  2301. var url = (file != null) ? file.getFolderUrl() : 'https://drive.google.com/open?id=' + id;
  2302. this.ui.editor.graph.openLink(url);
  2303. }), null, mxResources.get('open'), null, null, null, null, IMAGE_PATH + '/google-share.png');
  2304. this.ui.showDialog(dlg.container, 400, 150, true, true);
  2305. dlg.init();
  2306. });
  2307. if (this.sharingFailed)
  2308. {
  2309. fallback();
  2310. }
  2311. else
  2312. {
  2313. this.checkToken(mxUtils.bind(this, function()
  2314. {
  2315. try
  2316. {
  2317. var shareClient = new gapi.drive.share.ShareClient(this.appId);
  2318. shareClient.setOAuthToken(_token);
  2319. shareClient.setItemIds([id]);
  2320. shareClient.showSettingsDialog();
  2321. // Workaround for https://stackoverflow.com/questions/54753169 is to check
  2322. // if "sharing is unavailable" is showing and invoke a fallback dialog
  2323. if ('MutationObserver' in window)
  2324. {
  2325. if (this.sharingObserver != null)
  2326. {
  2327. this.sharingObserver.disconnect();
  2328. this.sharingObserver = null;
  2329. }
  2330. // Tries again even if observer was still around as the user may have
  2331. // closed the dialog while waiting. TODO: Find condition to disconnect
  2332. // observer when dialog is closed (use removedNodes?).
  2333. this.sharingObserver = new MutationObserver(mxUtils.bind(this, function(mutations)
  2334. {
  2335. var done = false;
  2336. for (var i = 0; i < mutations.length; i++)
  2337. {
  2338. for (var j = 0; j < mutations[i].addedNodes.length; j++)
  2339. {
  2340. var child = mutations[i].addedNodes[j];
  2341. if (child.nodeName == 'BUTTON' && child.getAttribute('name') == 'ok' &&
  2342. child.parentNode != null && child.parentNode.parentNode != null &&
  2343. child.parentNode.parentNode.getAttribute('role') == 'dialog')
  2344. {
  2345. this.sharingFailed = true;
  2346. child.click();
  2347. fallback();
  2348. done = true;
  2349. }
  2350. else if (child.nodeName == 'DIV' && child.className == 'shr-q-shr-r-shr-xb')
  2351. {
  2352. done = true;
  2353. }
  2354. }
  2355. }
  2356. if (done)
  2357. {
  2358. this.sharingObserver.disconnect();
  2359. this.sharingObserver = null;
  2360. }
  2361. }));
  2362. this.sharingObserver.observe(document, {childList: true, subtree: true});
  2363. }
  2364. }
  2365. catch (e)
  2366. {
  2367. this.ui.handleError(e);
  2368. }
  2369. }));
  2370. }
  2371. };
  2372. DriveClient.prototype.clearPersistentToken = function()
  2373. {
  2374. //Since we have multiple accounts now, full deletion is not possible
  2375. var authInfo = JSON.parse(this.getPersistentToken(true)) || {};
  2376. //Delete current user info
  2377. delete authInfo.current;
  2378. delete authInfo[this.userId];
  2379. //Set the next user as current
  2380. for (var id in authInfo)
  2381. {
  2382. authInfo.current = {userId: id, expires: 0}; //An expired token
  2383. break;
  2384. }
  2385. DrawioClient.prototype.setPersistentToken.call(this, JSON.stringify(authInfo));
  2386. };
  2387. DriveClient.prototype.setPersistentToken = function(userAuthInfo, sessionOnly)
  2388. {
  2389. var authInfo = JSON.parse(this.getPersistentToken(true)) || {};
  2390. userAuthInfo.userId = this.userId;
  2391. authInfo.current = userAuthInfo;
  2392. authInfo[this.userId] = {
  2393. user: this.user
  2394. };
  2395. DrawioClient.prototype.setPersistentToken.call(this, JSON.stringify(authInfo), sessionOnly);
  2396. };
  2397. })();