diff --git a/patches/jellyfin-apiclient+1.4.1.patch b/patches/jellyfin-apiclient+1.4.1.patch index 90d912a..7485df2 100644 --- a/patches/jellyfin-apiclient+1.4.1.patch +++ b/patches/jellyfin-apiclient+1.4.1.patch @@ -1,12 +1,22 @@ diff --git a/node_modules/jellyfin-apiclient/dist/jellyfin-apiclient.js b/node_modules/jellyfin-apiclient/dist/jellyfin-apiclient.js -index c2382cd..ae51ce1 100644 +index c2382cd..733887f 100644 --- a/node_modules/jellyfin-apiclient/dist/jellyfin-apiclient.js +++ b/node_modules/jellyfin-apiclient/dist/jellyfin-apiclient.js @@ -1,2 +1,6 @@ +-!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports["jellyfin-apiclient"]=t():e["jellyfin-apiclient"]=t()}(window,(function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";function n(e,t){if(!e)throw new Error("obj cannot be null!");e._callbacks=e._callbacks||{};var r=e._callbacks[t];return r||(e._callbacks[t]=[],r=e._callbacks[t]),r}r.r(t);var i={on:function(e,t,r){n(e,t).push(r)},off:function(e,t,r){var i=n(e,t),o=i.indexOf(r);-1!==o&&i.splice(o,1)},trigger:function(e,t){var r={type:t},i=[];i.push(r);for(var o=arguments[2]||[],a=0,s=o.length;a=r.length)return j(t,i);var o=r[n];return t.getDownloadSpeed(o.bytes).then((function(i){return i=20?Promise.reject():function(e){var t=[],r=[],n=e.serverInfo();return n.LocalAddress&&-1===r.indexOf(n.LocalAddress)&&(t.push({url:n.LocalAddress,timeout:0}),r.push(t[t.length-1].url)),n.ManualAddress&&-1===r.indexOf(n.ManualAddress)&&(t.push({url:n.ManualAddress,timeout:100}),r.push(t[t.length-1].url)),n.RemoteAddress&&-1===r.indexOf(n.RemoteAddress)&&(t.push({url:n.RemoteAddress,timeout:200}),r.push(t[t.length-1].url)),console.log("tryReconnect: "+r.join("|")),new Promise((function(r,n){var i={};i.numAddresses=t.length,i.rejects=0,t.map((function(t){setTimeout((function(){i.resolved||function(e,t,r,n,i){console.log("getTryConnectPromise "+t),I(e.getUrl("system/info/public",null,t),{method:"GET",accept:"application/json"},15e3).then((function(){r.resolved||(r.resolved=!0,console.log("Reconnect succeeded to "+t),e.serverAddress(t),n())}),(function(){r.resolved||(console.log("Reconnect failed to "+t),r.rejects++,r.rejects>=r.numAddresses&&i())}))}(e,t.url,i,r,n)}),t.timeout)}))}))}(t).catch((function(n){return console.log("error in tryReconnectInternal: "+(n||"")),new Promise((function(n,i){setTimeout((function(){e(t,r+1).then(n,i)}),500)}))}))}(r).then((function(){return console.log("Reconnect succeesed"),e.url=e.url.replace(i,r.serverAddress()),r.fetchWithFailover(e,!1)})).catch((function(t){throw console.log("Reconnect failed"),y(r,e.url,{}),t}))}))}},{key:"fetch",value:function(e,t){if(!e)return Promise.reject("Request cannot be null");if(e.headers=e.headers||{},!1!==t&&this.setRequestHeaders(e.headers),!1===this.enableAutomaticNetworking||"GET"!==e.type){console.log("Requesting url without automatic networking: ".concat(e.url));var r=this;return S(e).then((function(t){return r.lastFetch=(new Date).getTime(),t.status<400?"json"===e.dataType||"application/json"===e.headers.accept?t.json():"text"===e.dataType||0===(t.headers.get("Content-Type")||"").toLowerCase().indexOf("text/")?t.text():t:(y(r,e.url,t),Promise.reject(t))})).catch((function(t){return y(r,e.url,{}),Promise.reject(t)}))}return this.fetchWithFailover(e,!0)}},{key:"setAuthenticationInfo",value:function(e,t){this._currentUser=null,this._serverInfo.AccessToken=e,this._serverInfo.UserId=t,v(this)}},{key:"serverInfo",value:function(e){return e&&(this._serverInfo=e),this._serverInfo}},{key:"getCurrentUserId",value:function(){return this._serverInfo.UserId}},{key:"accessToken",value:function(){return this._serverInfo.AccessToken}},{key:"serverId",value:function(){return this.serverInfo().Id}},{key:"serverName",value:function(){return this.serverInfo().Name}},{key:"ajax",value:function(e,t){return e?this.fetch(e,t):Promise.reject("Request cannot be null")}},{key:"getCurrentUser",value:function(e){if(this._currentUser)return Promise.resolve(this._currentUser);var t=this.getCurrentUserId();if(!t)return Promise.reject();var r,n=this,i=this.getUser(t).then((function(e){return l.setItem("user-".concat(e.Id,"-").concat(e.ServerId),JSON.stringify(e)),n._currentUser=e,e})).catch((function(e){if(!e.status&&t&&n.accessToken()&&(r=T(n,t)))return Promise.resolve(r);throw e}));return!this.lastFetch&&!1!==e&&(r=T(n,t))?Promise.resolve(r):i}},{key:"isLoggedIn",value:function(){var e=this.serverInfo();return!!(e&&e.UserId&&e.AccessToken)}},{key:"logout",value:function(){var e=this;g(this),this.closeWebSocket();var t=function(){var t=e.serverInfo();t&&t.UserId&&t.Id&&l.removeItem("user-".concat(t.UserId,"-").concat(t.Id)),e.setAuthenticationInfo(null,null)};if(this.accessToken()){var r=this.getUrl("Sessions/Logout");return this.ajax({type:"POST",url:r}).then(t,t)}return t(),Promise.resolve()}},{key:"authenticateUserByName",value:function(e,t){var r=this;if(!e)return Promise.reject();var n=this.getUrl("Users/authenticatebyname");return new Promise((function(i,o){var a={Username:e,Pw:t||""};r.ajax({type:"POST",url:n,data:JSON.stringify(a),dataType:"json",contentType:"application/json"}).then((function(e){var t=function(){v(r),i(e)};r.onAuthenticated?r.onAuthenticated(r,e).then(t):t()})).catch(o)}))}},{key:"ensureWebSocket",value:function(){if(!this.isWebSocketOpenOrConnecting()&&this.isWebSocketSupported())try{this.openWebSocket()}catch(e){console.log("Error opening web socket: ".concat(e))}}},{key:"openWebSocket",value:function(){var e=this.accessToken();if(!e)throw new Error("Cannot open web socket without access token.");var t=this.getUrl("socket");t=p(t,"emby/socket","embywebsocket"),t=p(t,"https:","wss:"),t=p(t,"http:","ws:"),t+="?api_key=".concat(e),t+="&deviceId=".concat(this.deviceId()),console.log("opening web socket with url: ".concat(t));var r,n,o=new WebSocket(t);o.onmessage=P.bind(this),o.onopen=E.bind(this),o.onerror=A.bind(this),r=this,(n=o).onclose=function(){console.log("web socket closed"),O(n),r._webSocket===n&&(console.log("nulling out web socket"),r._webSocket=null),setTimeout((function(){i.trigger(r,"websocketclose")}),0)},this._webSocket=o}},{key:"closeWebSocket",value:function(){var e=this._webSocket;e&&e.readyState===WebSocket.OPEN&&e.close()}},{key:"sendWebSocketMessage",value:function(e,t){console.log("Sending web socket message: ".concat(e));var r={MessageType:e};t&&(r.Data=t),r=JSON.stringify(r),this._webSocket.send(r)}},{key:"sendMessage",value:function(e,t){this.isWebSocketOpen()&&this.sendWebSocketMessage(e,t)}},{key:"isMessageChannelOpen",value:function(){return this.isWebSocketOpen()}},{key:"isWebSocketOpen",value:function(){var e=this._webSocket;return!!e&&e.readyState===WebSocket.OPEN}},{key:"isWebSocketOpenOrConnecting",value:function(){var e=this._webSocket;return!!e&&(e.readyState===WebSocket.OPEN||e.readyState===WebSocket.CONNECTING)}},{key:"get",value:function(e){return this.ajax({type:"GET",url:e})}},{key:"getJSON",value:function(e,t){return this.fetch({url:e,type:"GET",dataType:"json",headers:{accept:"application/json"}},t)}},{key:"updateServerInfo",value:function(e,t){if(null==e)throw new Error("server cannot be null");if(this.serverInfo(e),!t)throw new Error("serverUrl cannot be null. serverInfo: ".concat(JSON.stringify(e)));console.log("Setting server address to ".concat(t)),this.serverAddress(t)}},{key:"isWebSocketSupported",value:function(){try{return null!=WebSocket}catch(e){return!1}}},{key:"clearAuthenticationInfo",value:function(){this.setAuthenticationInfo(null,null)}},{key:"encodeName",value:function(e){var t=m({name:e=(e=(e=e.split("/").join("-")).split("&").join("-")).split("?").join("-")});return t.substring(t.indexOf("=")+1).replace("'","%27")}},{key:"getServerTime",value:function(){var e=this.getUrl("GetUTCTime");return this.ajax({type:"GET",url:e})}},{key:"getDownloadSpeed",value:function(e){var t=this.getUrl("Playback/BitrateTest",{Size:e}),r=(new Date).getTime();return this.ajax({type:"GET",url:t,timeout:5e3}).then((function(){var t=((new Date).getTime()-r)/1e3,n=e/t;return Math.round(8*n)}))}},{key:"detectBitrate",value:function(e){if(!e&&this.lastDetectedBitrate&&(new Date).getTime()-(this.lastDetectedBitrateTime||0)<=36e5)return Promise.resolve(this.lastDetectedBitrate);var t=this;return this.getEndpointInfo().then((function(e){return N(t,e)}),(function(e){return N(t,{})}))}},{key:"getItem",value:function(e,t){if(!t)throw new Error("null itemId");var r=e?this.getUrl("Users/".concat(e,"/Items/").concat(t)):this.getUrl("Items/".concat(t));return this.getJSON(r)}},{key:"getRootFolder",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/Items/Root"));return this.getJSON(t)}},{key:"getNotificationSummary",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Notifications/".concat(e,"/Summary"));return this.getJSON(t)}},{key:"getNotifications",value:function(e,t){if(!e)throw new Error("null userId");var r=this.getUrl("Notifications/".concat(e),t||{});return this.getJSON(r)}},{key:"markNotificationsRead",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null idList");var n=r?"Read":"Unread",i={UserId:e,Ids:t.join(",")},o=this.getUrl("Notifications/".concat(e,"/").concat(n),i);return this.ajax({type:"POST",url:o})}},{key:"getRemoteImageProviders",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages/Providers"),e);return this.getJSON(r)}},{key:"getAvailableRemoteImages",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages"),e);return this.getJSON(r)}},{key:"downloadRemoteImage",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages/Download"),e);return this.ajax({type:"POST",url:r})}},{key:"getRecordingFolders",value:function(e){var t=this.getUrl("LiveTv/Recordings/Folders",{userId:e});return this.getJSON(t)}},{key:"getLiveTvInfo",value:function(e){var t=this.getUrl("LiveTv/Info",e||{});return this.getJSON(t)}},{key:"getLiveTvGuideInfo",value:function(e){var t=this.getUrl("LiveTv/GuideInfo",e||{});return this.getJSON(t)}},{key:"getLiveTvChannel",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Channels/".concat(e),r);return this.getJSON(n)}},{key:"getLiveTvChannels",value:function(e){var t=this.getUrl("LiveTv/Channels",e||{});return this.getJSON(t)}},{key:"getLiveTvPrograms",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return e.channelIds&&e.channelIds.length>1800?this.ajax({type:"POST",url:this.getUrl("LiveTv/Programs"),data:JSON.stringify(e),contentType:"application/json",dataType:"json"}):this.ajax({type:"GET",url:this.getUrl("LiveTv/Programs",e),dataType:"json"})}},{key:"getLiveTvRecommendedPrograms",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.ajax({type:"GET",url:this.getUrl("LiveTv/Programs/Recommended",e),dataType:"json"})}},{key:"getLiveTvRecordings",value:function(e){var t=this.getUrl("LiveTv/Recordings",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingSeries",value:function(e){var t=this.getUrl("LiveTv/Recordings/Series",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingGroups",value:function(e){var t=this.getUrl("LiveTv/Recordings/Groups",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingGroup",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Recordings/Groups/".concat(e));return this.getJSON(t)}},{key:"getLiveTvRecording",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Recordings/".concat(e),r);return this.getJSON(n)}},{key:"getLiveTvProgram",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Programs/".concat(e),r);return this.getJSON(n)}},{key:"deleteLiveTvRecording",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Recordings/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"cancelLiveTvTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Timers/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"getLiveTvTimers",value:function(e){var t=this.getUrl("LiveTv/Timers",e||{});return this.getJSON(t)}},{key:"getLiveTvTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Timers/".concat(e));return this.getJSON(t)}},{key:"getNewLiveTvTimerDefaults",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("LiveTv/Timers/Defaults",e);return this.getJSON(t)}},{key:"createLiveTvTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/Timers");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateLiveTvTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/Timers/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"resetLiveTvTuner",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Tuners/".concat(e,"/Reset"));return this.ajax({type:"POST",url:t})}},{key:"getLiveTvSeriesTimers",value:function(e){var t=this.getUrl("LiveTv/SeriesTimers",e||{});return this.getJSON(t)}},{key:"getLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e));return this.getJSON(t)}},{key:"cancelLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"createLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/SeriesTimers");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"getRegistrationInfo",value:function(e){var t=this.getUrl("Registrations/".concat(e));return this.getJSON(t)}},{key:"getSystemInfo",value:function(e){var t=this.getUrl("System/Info"),r=this;return this.getJSON(t).then((function(e){return r.setSystemInfo(e),Promise.resolve(e)}))}},{key:"getSyncStatus",value:function(){var e=this.getUrl("Sync/"+itemId+"/Status");return this.ajax({url:e,type:"POST",dataType:"json",contentType:"application/json",data:JSON.stringify({TargetId:this.deviceId()})})}},{key:"getPublicSystemInfo",value:function(){var e=this.getUrl("System/Info/Public"),t=this;return this.getJSON(e).then((function(e){return t.setSystemInfo(e),Promise.resolve(e)}))}},{key:"getInstantMixFromItem",value:function(e,t){var r=this.getUrl("Items/".concat(e,"/InstantMix"),t);return this.getJSON(r)}},{key:"getEpisodes",value:function(e,t){var r=this.getUrl("Shows/".concat(e,"/Episodes"),t);return this.getJSON(r)}},{key:"getDisplayPreferences",value:function(e,t,r){var n=this.getUrl("DisplayPreferences/".concat(e),{userId:t,client:r});return this.getJSON(n)}},{key:"updateDisplayPreferences",value:function(e,t,r,n){var i=this.getUrl("DisplayPreferences/".concat(e),{userId:r,client:n});return this.ajax({type:"POST",url:i,data:JSON.stringify(t),contentType:"application/json"})}},{key:"getSeasons",value:function(e,t){var r=this.getUrl("Shows/".concat(e,"/Seasons"),t);return this.getJSON(r)}},{key:"getSimilarItems",value:function(e,t){var r=this.getUrl("Items/".concat(e,"/Similar"),t);return this.getJSON(r)}},{key:"getCultures",value:function(){var e=this.getUrl("Localization/cultures");return this.getJSON(e)}},{key:"getCountries",value:function(){var e=this.getUrl("Localization/countries");return this.getJSON(e)}},{key:"getPlaybackInfo",value:function(e,t,r){var n={DeviceProfile:r};return this.ajax({url:this.getUrl("Items/".concat(e,"/PlaybackInfo"),t),type:"POST",data:JSON.stringify(n),contentType:"application/json",dataType:"json"})}},{key:"getLiveStreamMediaInfo",value:function(e){var t={LiveStreamId:e};return this.ajax({url:this.getUrl("LiveStreams/MediaInfo"),type:"POST",data:JSON.stringify(t),contentType:"application/json",dataType:"json"})}},{key:"getIntros",value:function(e){return this.getJSON(this.getUrl("Users/".concat(this.getCurrentUserId(),"/Items/").concat(e,"/Intros")))}},{key:"getDirectoryContents",value:function(e,t){if(!e)throw new Error("null path");if("string"!=typeof e)throw new Error("invalid path");(t=t||{}).path=e;var r=this.getUrl("Environment/DirectoryContents",t);return this.getJSON(r)}},{key:"getNetworkShares",value:function(e){if(!e)throw new Error("null path");var t={};t.path=e;var r=this.getUrl("Environment/NetworkShares",t);return this.getJSON(r)}},{key:"getParentPath",value:function(e){if(!e)throw new Error("null path");var t={};t.path=e;var r=this.getUrl("Environment/ParentPath",t);return this.ajax({type:"GET",url:r,dataType:"text"})}},{key:"getDrives",value:function(){var e=this.getUrl("Environment/Drives");return this.getJSON(e)}},{key:"getNetworkDevices",value:function(){var e=this.getUrl("Environment/NetworkDevices");return this.getJSON(e)}},{key:"cancelPackageInstallation",value:function(e){if(!e)throw new Error("null installationId");var t=this.getUrl("Packages/Installing/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"refreshItem",value:function(e,t){if(!e)throw new Error("null itemId");var r=this.getUrl("Items/".concat(e,"/Refresh"),t||{});return this.ajax({type:"POST",url:r})}},{key:"installPlugin",value:function(e,t,r){if(!e)throw new Error("null name");var n={AssemblyGuid:t};r&&(n.version=r);var i=this.getUrl("Packages/Installed/".concat(e),n);return this.ajax({type:"POST",url:i})}},{key:"restartServer",value:function(){var e=this.getUrl("System/Restart");return this.ajax({type:"POST",url:e})}},{key:"shutdownServer",value:function(){var e=this.getUrl("System/Shutdown");return this.ajax({type:"POST",url:e})}},{key:"getPackageInfo",value:function(e,t){if(!e)throw new Error("null name");var r={AssemblyGuid:t},n=this.getUrl("Packages/".concat(e),r);return this.getJSON(n)}},{key:"getVirtualFolders",value:function(){var e="Library/VirtualFolders";return e=this.getUrl(e),this.getJSON(e)}},{key:"getPhysicalPaths",value:function(){var e=this.getUrl("Library/PhysicalPaths");return this.getJSON(e)}},{key:"getServerConfiguration",value:function(){var e=this.getUrl("System/Configuration");return this.getJSON(e)}},{key:"getDevicesOptions",value:function(){var e=this.getUrl("System/Configuration/devices");return this.getJSON(e)}},{key:"getContentUploadHistory",value:function(){var e=this.getUrl("Devices/CameraUploads",{DeviceId:this.deviceId()});return this.getJSON(e)}},{key:"getNamedConfiguration",value:function(e){var t=this.getUrl("System/Configuration/".concat(e));return this.getJSON(t)}},{key:"getScheduledTasks",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("ScheduledTasks",e);return this.getJSON(t)}},{key:"startScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/Running/".concat(e));return this.ajax({type:"POST",url:t})}},{key:"getScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/".concat(e));return this.getJSON(t)}},{key:"getNextUpEpisodes",value:function(e){var t=this.getUrl("Shows/NextUp",e);return this.getJSON(t)}},{key:"stopScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/Running/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"getPluginConfiguration",value:function(e){if(!e)throw new Error("null Id");var t=this.getUrl("Plugins/".concat(e,"/Configuration"));return this.getJSON(t)}},{key:"getAvailablePlugins",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};e.PackageType="UserInstalled";var t=this.getUrl("Packages",e);return this.getJSON(t)}},{key:"uninstallPlugin",value:function(e){if(!e)throw new Error("null Id");var t=this.getUrl("Plugins/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"removeVirtualFolder",value:function(e,t){if(!e)throw new Error("null name");var r="Library/VirtualFolders";return r=this.getUrl(r,{refreshLibrary:!!t,name:e}),this.ajax({type:"DELETE",url:r})}},{key:"addVirtualFolder",value:function(e,t,r,n){if(!e)throw new Error("null name");var i={};t&&(i.collectionType=t),i.refreshLibrary=!!r,i.name=e;var o="Library/VirtualFolders";return o=this.getUrl(o,i),this.ajax({type:"POST",url:o,data:JSON.stringify({LibraryOptions:n}),contentType:"application/json"})}},{key:"updateVirtualFolderOptions",value:function(e,t){if(!e)throw new Error("null name");var r="Library/VirtualFolders/LibraryOptions";return r=this.getUrl(r),this.ajax({type:"POST",url:r,data:JSON.stringify({Id:e,LibraryOptions:t}),contentType:"application/json"})}},{key:"renameVirtualFolder",value:function(e,t,r){if(!e)throw new Error("null name");var n="Library/VirtualFolders/Name";return n=this.getUrl(n,{refreshLibrary:!!r,newName:t,name:e}),this.ajax({type:"POST",url:n})}},{key:"addMediaPath",value:function(e,t,r,n){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null mediaPath");var i="Library/VirtualFolders/Paths",o={Path:t};return r&&(o.NetworkPath=r),i=this.getUrl(i,{refreshLibrary:!!n}),this.ajax({type:"POST",url:i,data:JSON.stringify({Name:e,PathInfo:o}),contentType:"application/json"})}},{key:"updateMediaPath",value:function(e,t){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null pathInfo");var r="Library/VirtualFolders/Paths/Update";return r=this.getUrl(r),this.ajax({type:"POST",url:r,data:JSON.stringify({Name:e,PathInfo:t}),contentType:"application/json"})}},{key:"removeMediaPath",value:function(e,t,r){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null mediaPath");var n="Library/VirtualFolders/Paths";return n=this.getUrl(n,{refreshLibrary:!!r,path:t,name:e}),this.ajax({type:"DELETE",url:n})}},{key:"deleteUser",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("Users/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"deleteUserImage",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null imageType");var n=this.getUrl("Users/".concat(e,"/Images/").concat(t));return null!=r&&(n+="/".concat(r)),this.ajax({type:"DELETE",url:n})}},{key:"deleteItemImage",value:function(e,t,r){if(!t)throw new Error("null imageType");var n=this.getUrl("Items/".concat(e,"/Images"));return n+="/".concat(t),null!=r&&(n+="/".concat(r)),this.ajax({type:"DELETE",url:n})}},{key:"deleteItem",value:function(e){if(!e)throw new Error("null itemId");var t=this.getUrl("Items/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"stopActiveEncodings",value:function(e){var t={deviceId:this.deviceId()};e&&(t.PlaySessionId=e);var r=this.getUrl("Videos/ActiveEncodings",t);return this.ajax({type:"DELETE",url:r})}},{key:"reportCapabilities",value:function(e){var t=this.getUrl("Sessions/Capabilities/Full");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateItemImageIndex",value:function(e,t,r,n){if(!t)throw new Error("null imageType");var i={newIndex:n},o=this.getUrl("Items/".concat(e,"/Images/").concat(t,"/").concat(r,"/Index"),i);return this.ajax({type:"POST",url:o})}},{key:"getItemImageInfos",value:function(e){var t=this.getUrl("Items/".concat(e,"/Images"));return this.getJSON(t)}},{key:"getCriticReviews",value:function(e,t){if(!e)throw new Error("null itemId");var r=this.getUrl("Items/".concat(e,"/CriticReviews"),t);return this.getJSON(r)}},{key:"getItemDownloadUrl",value:function(e){if(!e)throw new Error("itemId cannot be empty");var t="Items/".concat(e,"/Download");return this.getUrl(t,{api_key:this.accessToken()})}},{key:"getSessions",value:function(e){var t=this.getUrl("Sessions",e);return this.getJSON(t)}},{key:"uploadUserImage",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null imageType");if(!r)throw new Error("File must be an image.");if(!r.type.startsWith("image/"))throw new Error("File must be an image.");var n=this;return new Promise((function(i,o){var a=new FileReader;a.onerror=function(){o()},a.onabort=function(){o()},a.onload=function(a){var s=a.target.result.split(",")[1],u=n.getUrl("Users/".concat(e,"/Images/").concat(t));n.ajax({type:"POST",url:u,data:s,contentType:"image/".concat(r.name.substring(r.name.lastIndexOf(".")+1))}).then(i,o)},a.readAsDataURL(r)}))}},{key:"uploadItemImage",value:function(e,t,r){if(!e)throw new Error("null itemId");if(!t)throw new Error("null imageType");if(!r)throw new Error("File must be an image.");if(!r.type.startsWith("image/"))throw new Error("File must be an image.");var n=this.getUrl("Items/".concat(e,"/Images"));n+="/".concat(t);var i=this;return new Promise((function(e,t){var o=new FileReader;o.onerror=function(){t()},o.onabort=function(){t()},o.onload=function(o){var a=o.target.result.split(",")[1];i.ajax({type:"POST",url:n,data:a,contentType:"image/".concat(r.name.substring(r.name.lastIndexOf(".")+1))}).then(e,t)},o.readAsDataURL(r)}))}},{key:"getInstalledPlugins",value:function(){var e=this.getUrl("Plugins",{});return this.getJSON(e)}},{key:"getUser",value:function(e){if(!e)throw new Error("Must supply a userId");var t=this.getUrl("Users/".concat(e));return this.getJSON(t)}},{key:"getStudio",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Studios/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getGenre",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Genres/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getMusicGenre",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("MusicGenres/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getArtist",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Artists/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getPerson",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Persons/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getPublicUsers",value:function(){var e=this.getUrl("users/public");return this.ajax({type:"GET",url:e,dataType:"json"},!1)}},{key:"getUsers",value:function(e){var t=this.getUrl("users",e||{});return this.getJSON(t)}},{key:"getParentalRatings",value:function(){var e=this.getUrl("Localization/ParentalRatings");return this.getJSON(e)}},{key:"getDefaultImageQuality",value:function(e){return"backdrop"===e.toLowerCase()?80:90}},{key:"getUserImageUrl",value:function(e,t){if(!e)throw new Error("null userId");t=t||{};var r="Users/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),x(this,t),delete t.type,delete t.index,this.getUrl(r,t)}},{key:"getImageUrl",value:function(e,t){if(!e)throw new Error("itemId cannot be empty");t=t||{};var r="Items/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),t.quality=t.quality||this.getDefaultImageQuality(t.type),this.normalizeImageOptions&&this.normalizeImageOptions(t),delete t.type,delete t.index,this.getUrl(r,t)}},{key:"getScaledImageUrl",value:function(e,t){if(!e)throw new Error("itemId cannot be empty");t=t||{};var r="Items/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),x(this,t),delete t.type,delete t.index,delete t.minScale,this.getUrl(r,t)}},{key:"getThumbImageUrl",value:function(e,t){if(!e)throw new Error("null item");return(t=t||{}).imageType="thumb",e.ImageTags&&e.ImageTags.Thumb?(t.tag=e.ImageTags.Thumb,this.getImageUrl(e.Id,t)):e.ParentThumbItemId?(t.tag=e.ImageTags.ParentThumbImageTag,this.getImageUrl(e.ParentThumbItemId,t)):null}},{key:"updateUserPassword",value:function(e,t,r){if(!e)return Promise.reject();var n=this.getUrl("Users/".concat(e,"/Password"));return this.ajax({type:"POST",url:n,data:JSON.stringify({CurrentPw:t||"",NewPw:r}),contentType:"application/json"})}},{key:"updateEasyPassword",value:function(e,t){if(e){var r=this.getUrl("Users/".concat(e,"/EasyPassword"));return this.ajax({type:"POST",url:r,data:{NewPw:t}})}Promise.reject()}},{key:"resetUserPassword",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/Password")),r={resetPassword:!0};return this.ajax({type:"POST",url:t,data:r})}},{key:"resetEasyPassword",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/EasyPassword")),r={resetPassword:!0};return this.ajax({type:"POST",url:t,data:r})}},{key:"updateServerConfiguration",value:function(e){if(!e)throw new Error("null configuration");var t=this.getUrl("System/Configuration");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateNamedConfiguration",value:function(e,t){if(!t)throw new Error("null configuration");var r=this.getUrl("System/Configuration/".concat(e));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateItem",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("Items/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updatePluginSecurityInfo",value:function(e){var t=this.getUrl("Plugins/SecurityInfo");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"createUser",value:function(e){var t=this.getUrl("Users/New");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json",headers:{accept:"application/json"}})}},{key:"updateUser",value:function(e){if(!e)throw new Error("null user");var t=this.getUrl("Users/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateUserPolicy",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null policy");var r=this.getUrl("Users/".concat(e,"/Policy"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateUserConfiguration",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null configuration");var r=this.getUrl("Users/".concat(e,"/Configuration"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateScheduledTaskTriggers",value:function(e,t){if(!e)throw new Error("null id");if(!t)throw new Error("null triggers");var r=this.getUrl("ScheduledTasks/".concat(e,"/Triggers"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updatePluginConfiguration",value:function(e,t){if(!e)throw new Error("null Id");if(!t)throw new Error("null configuration");var r=this.getUrl("Plugins/".concat(e,"/Configuration"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"getAncestorItems",value:function(e,t){if(!e)throw new Error("null itemId");var r={};t&&(r.userId=t);var n=this.getUrl("Items/".concat(e,"/Ancestors"),r);return this.getJSON(n)}},{key:"getItems",value:function(e,t){var r;return r="string"===c(e).toString().toLowerCase()?this.getUrl("Users/".concat(e,"/Items"),t):this.getUrl("Items",t),this.getJSON(r)}},{key:"getResumableItems",value:function(e,t){return this.isMinServerVersion("3.2.33")?this.getJSON(this.getUrl("Users/".concat(e,"/Items/Resume"),t)):this.getItems(e,Object.assign({SortBy:"DatePlayed",SortOrder:"Descending",Filters:"IsResumable",Recursive:!0,CollapseBoxSetItems:!1,ExcludeLocationTypes:"Virtual"},t))}},{key:"getMovieRecommendations",value:function(e){return this.getJSON(this.getUrl("Movies/Recommendations",e))}},{key:"getUpcomingEpisodes",value:function(e){return this.getJSON(this.getUrl("Shows/Upcoming",e))}},{key:"getUserViews",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0,r=this.getUrl("Users/".concat(t||this.getCurrentUserId(),"/Views"),e);return this.getJSON(r)}},{key:"getArtists",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Artists",t);return this.getJSON(r)}},{key:"getAlbumArtists",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Artists/AlbumArtists",t);return this.getJSON(r)}},{key:"getGenres",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Genres",t);return this.getJSON(r)}},{key:"getMusicGenres",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("MusicGenres",t);return this.getJSON(r)}},{key:"getPeople",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Persons",t);return this.getJSON(r)}},{key:"getStudios",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Studios",t);return this.getJSON(r)}},{key:"getLocalTrailers",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/LocalTrailers"));return this.getJSON(r)}},{key:"getAdditionalVideoParts",value:function(e,t){if(!t)throw new Error("null itemId");var r={};e&&(r.userId=e);var n=this.getUrl("Videos/".concat(t,"/AdditionalParts"),r);return this.getJSON(n)}},{key:"getThemeMedia",value:function(e,t,r){if(!t)throw new Error("null itemId");var n={};e&&(n.userId=e),n.InheritFromParent=r||!1;var i=this.getUrl("Items/".concat(t,"/ThemeMedia"),n);return this.getJSON(i)}},{key:"getSearchHints",value:function(e){var t=this.getUrl("Search/Hints",e),r=this.serverId();return this.getJSON(t).then((function(e){return e.SearchHints.forEach((function(e){e.ServerId=r})),e}))}},{key:"getSpecialFeatures",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/SpecialFeatures"));return this.getJSON(r)}},{key:"getDateParamValue",value:function(e){function t(e){return e<10?"0".concat(e):e}var r=e;return"".concat(r.getFullYear()).concat(t(r.getMonth()+1)).concat(t(r.getDate())).concat(t(r.getHours())).concat(t(r.getMinutes())).concat(t(r.getSeconds()))}},{key:"markPlayed",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n={};r&&(n.DatePlayed=this.getDateParamValue(r));var i=this.getUrl("Users/".concat(e,"/PlayedItems/").concat(t),n);return this.ajax({type:"POST",url:i,dataType:"json"})}},{key:"markUnplayed",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/PlayedItems/").concat(t));return this.ajax({type:"DELETE",url:r,dataType:"json"})}},{key:"updateFavoriteStatus",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n=this.getUrl("Users/".concat(e,"/FavoriteItems/").concat(t)),i=r?"POST":"DELETE";return this.ajax({type:i,url:n,dataType:"json"})}},{key:"updateUserItemRating",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/Rating"),{likes:r});return this.ajax({type:"POST",url:n,dataType:"json"})}},{key:"getItemCounts",value:function(e){var t={};e&&(t.userId=e);var r=this.getUrl("Items/Counts",t);return this.getJSON(r)}},{key:"clearUserItemRating",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/Rating"));return this.ajax({type:"DELETE",url:r,dataType:"json"})}},{key:"reportPlaybackStart",value:function(e){if(!e)throw new Error("null options");this.lastPlaybackProgressReport=0,this.lastPlaybackProgressReportTicks=null,g(this),w(this);var t=this.getUrl("Sessions/Playing");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"reportPlaybackProgress",value:function(e){if(!e)throw new Error("null options");var t=e.EventName||"timeupdate",r=h[t]||0,n=(new Date).getTime()-(this.lastPlaybackProgressReport||0),i=e.PositionTicks;if(n=5e7&&(r=0)}if(r<(void 0!==this.reportPlaybackProgressTimeout?this.reportPlaybackProgressTimeout:1e6)&&w(this),this.lastPlaybackProgressOptions=e,this.reportPlaybackProgressPromise)return Promise.resolve();var a,s=this,u=!1,l=function(){s.reportPlaybackProgressPromise===a&&(delete s.lastPlaybackProgressOptions,delete s.reportPlaybackProgressTimeout,delete s.reportPlaybackProgressPromise,delete s.reportPlaybackProgressCancel)},c=Math.max(0,r-n);return a=new Promise((function(e,t){return setTimeout(e,c)})).then((function(){return u?Promise.resolve():function(e){if(l(),!e)throw new Error("null options");s.lastPlaybackProgressReport=(new Date).getTime(),s.lastPlaybackProgressReportTicks=e.PositionTicks;var t=s.getUrl("Sessions/Playing/Progress");return s.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}(s.lastPlaybackProgressOptions)})).finally((function(){l()})),this.reportPlaybackProgressTimeout=r,this.reportPlaybackProgressPromise=a,this.reportPlaybackProgressCancel=function(){u=!0,l()},a}},{key:"reportOfflineActions",value:function(e){if(!e)throw new Error("null actions");var t=this.getUrl("Sync/OfflineActions");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"syncData",value:function(e){if(!e)throw new Error("null data");var t=this.getUrl("Sync/Data");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t,dataType:"json"})}},{key:"getReadySyncItems",value:function(e){if(!e)throw new Error("null deviceId");var t=this.getUrl("Sync/Items/Ready",{TargetId:e});return this.getJSON(t)}},{key:"reportSyncJobItemTransferred",value:function(e){if(!e)throw new Error("null syncJobItemId");var t=this.getUrl("Sync/JobItems/".concat(e,"/Transferred"));return this.ajax({type:"POST",url:t})}},{key:"cancelSyncItems",value:function(e,t){if(!e)throw new Error("null itemIds");var r=this.getUrl("Sync/".concat(t||this.deviceId(),"/Items"),{ItemIds:e.join(",")});return this.ajax({type:"DELETE",url:r})}},{key:"reportPlaybackStopped",value:function(e){if(!e)throw new Error("null options");this.lastPlaybackProgressReport=0,this.lastPlaybackProgressReportTicks=null,v(this),w(this);var t=this.getUrl("Sessions/Playing/Stopped");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"sendPlayCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null options");var r=this.getUrl("Sessions/".concat(e,"/Playing"),t);return this.ajax({type:"POST",url:r})}},{key:"sendCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null command");var r={type:"POST",url:this.getUrl("Sessions/".concat(e,"/Command"))};return r.data=JSON.stringify(t),r.contentType="application/json",this.ajax(r)}},{key:"sendMessageCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null options");var r={type:"POST",url:this.getUrl("Sessions/".concat(e,"/Message"))};return r.data=JSON.stringify(t),r.contentType="application/json",this.ajax(r)}},{key:"sendPlayStateCommand",value:function(e,t,r){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null command");var n=this.getUrl("Sessions/".concat(e,"/Playing/").concat(t),r||{});return this.ajax({type:"POST",url:n})}},{key:"getSyncPlayGroups",value:function(){var e=this.getUrl("SyncPlay/List");return this.ajax({type:"GET",url:e})}},{key:"createSyncPlayGroup",value:function(){var e=this.getUrl("SyncPlay/New");return this.ajax({type:"POST",url:e})}},{key:"joinSyncPlayGroup",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Join",e);return this.ajax({type:"POST",url:t})}},{key:"leaveSyncPlayGroup",value:function(){var e=this.getUrl("SyncPlay/Leave");return this.ajax({type:"POST",url:e})}},{key:"sendSyncPlayPing",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Ping",e);return this.ajax({type:"POST",url:t})}},{key:"requestSyncPlayStart",value:function(){var e=this.getUrl("SyncPlay/Play");return this.ajax({type:"POST",url:e})}},{key:"requestSyncPlayPause",value:function(){var e=this.getUrl("SyncPlay/Pause");return this.ajax({type:"POST",url:e})}},{key:"requestSyncPlaySeek",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Seek",e);return this.ajax({type:"POST",url:t})}},{key:"createPackageReview",value:function(e){var t=this.getUrl("Packages/Reviews/".concat(e.id),e);return this.ajax({type:"POST",url:t})}},{key:"getPackageReviews",value:function(e,t,r,n){if(!e)throw new Error("null packageId");var i={};t&&(i.MinRating=t),r&&(i.MaxRating=r),n&&(i.Limit=n);var o=this.getUrl("Packages/".concat(e,"/Reviews"),i);return this.getJSON(o)}},{key:"getSavedEndpointInfo",value:function(){return this._endPointInfo}},{key:"getEndpointInfo",value:function(){var e=this._endPointInfo;if(e)return Promise.resolve(e);var t=this;return this.getJSON(this.getUrl("System/Endpoint")).then((function(e){return k(t,e),e}))}},{key:"getLatestItems",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.getJSON(this.getUrl("Users/".concat(this.getCurrentUserId(),"/Items/Latest"),e))}},{key:"getFilters",value:function(e){return this.getJSON(this.getUrl("Items/Filters2",e))}},{key:"setSystemInfo",value:function(e){this._serverVersion=e.Version}},{key:"serverVersion",value:function(){return this._serverVersion}},{key:"isMinServerVersion",value:function(e){var t=this.serverVersion();return!!t&&function(e,t){e=e.split("."),t=t.split(".");for(var r=0,n=Math.max(e.length,t.length);ro)return 1}return 0}(t,e)>=0}},{key:"handleMessageReceived",value:function(e){b(this,e)}}])&&d(t.prototype,r),n&&d(t,n),e}();function J(e){return(J="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function R(e,t){for(var r=0;rt.length&&0===e.indexOf(t))}function H(e,t){return q(e,t)?e.substr(t.length):e}function z(e){return e?V(e)?e:"local:".concat(e):null}function K(e){e.Id=z(e.Id),e.SeriesId=z(e.SeriesId),e.SeasonId=z(e.SeasonId),e.AlbumId=z(e.AlbumId),e.ParentId=z(e.ParentId),e.ParentThumbItemId=z(e.ParentThumbItemId),e.ParentPrimaryImageItemId=z(e.ParentPrimaryImageItemId),e.PrimaryImageItemId=z(e.PrimaryImageItemId),e.ParentLogoItemId=z(e.ParentLogoItemId),e.ParentBackdropItemId=z(e.ParentBackdropItemId),e.ParentBackdropImageTags=null}function Q(e,t,r){return e.getLocalFolders(t,r).then((function(r){var n=null;return r.length>0&&(n={Name:e.downloadsTitleText||"Downloads",ServerId:t,Id:"localview",Type:"localview",IsFolder:!0}),Promise.resolve(n)}))}var X=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&D(e,t)}(o,e);var t,r,n,i=M(o);function o(e,t,r,n,a,s,u){var l;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,o),(l=i.call(this,e,t,r,n,a,s)).localAssetManager=u,l}return t=o,(r=[{key:"getPlaybackInfo",value:function(e,t,r){var n=function(){return C.prototype.getPlaybackInfo.call(i,e,t,r)};if(V(e))return this.localAssetManager.getLocalItem(this.serverId(),W(e)).then((function(e){return{MediaSources:e.Item.MediaSources.map((function(e){return e.SupportsDirectPlay=!0,e.SupportsDirectStream=!1,e.SupportsTranscoding=!1,e.IsLocal=!0,e}))}}),n);var i=this;return this.localAssetManager.getLocalItem(this.serverId(),e).then((function(o){if(o){var a=o.Item.MediaSources.map((function(e){return e.SupportsDirectPlay=!0,e.SupportsDirectStream=!1,e.SupportsTranscoding=!1,e.IsLocal=!0,e}));return i.localAssetManager.fileExists(o.LocalPath).then((function(n){if(n){var o={MediaSources:a};return Promise.resolve(o)}return C.prototype.getPlaybackInfo.call(i,e,t,r)}),n)}return C.prototype.getPlaybackInfo.call(i,e,t,r)}),n)}},{key:"getItems",value:function(e,t){var r,n=this.serverInfo();if(n&&"localview"===t.ParentId)return this.getLocalFolders(n.Id,e).then((function(e){var t={Items:e,TotalRecordCount:e.length};return Promise.resolve(t)}));if(n&&t&&(V(t.ParentId)||V(t.SeriesId)||V(t.SeasonId)||G(t.ParentId)||V(t.AlbumIds)))return this.localAssetManager.getViewItems(n.Id,e,t).then((function(e){e.forEach((function(e){K(e)}));var t={Items:e,TotalRecordCount:e.length};return Promise.resolve(t)}));if(t&&t.ExcludeItemIds&&t.ExcludeItemIds.length){var i=t.ExcludeItemIds.split(",");for(r=0;r0?Promise.resolve(r[0]):Promise.reject()})):V(t)&&(r=this.serverInfo())?this.localAssetManager.getLocalItem(r.Id,W(t)).then((function(e){return K(e.Item),Promise.resolve(e.Item)})):C.prototype.getItem.call(this,e,t)}},{key:"getLocalFolders",value:function(e){var t=this.serverInfo();return e=e||t.UserId,this.localAssetManager.getViews(t.Id,e)}},{key:"getNextUpEpisodes",value:function(e){return e.SeriesId&&V(e.SeriesId)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getNextUpEpisodes.call(this,e)}},{key:"getSeasons",value:function(e,t){return V(e)?(t.SeriesId=e,t.IncludeItemTypes="Season",this.getItems(this.getCurrentUserId(),t)):C.prototype.getSeasons.call(this,e,t)}},{key:"getEpisodes",value:function(e,t){return V(t.SeasonId)||V(t.seasonId)||V(e)?(t.SeriesId=e,t.IncludeItemTypes="Episode",this.getItems(this.getCurrentUserId(),t)):C.prototype.getEpisodes.call(this,e,t)}},{key:"getLatestOfflineItems",value:function(e){e.SortBy="DateCreated",e.SortOrder="Descending";var t=this.serverInfo();return t?this.localAssetManager.getViewItems(t.Id,null,e).then((function(e){return e.forEach((function(e){K(e)})),Promise.resolve(e)})):Promise.resolve([])}},{key:"getThemeMedia",value:function(e,t,r){return G(t)||V(t)||B(t)?Promise.reject():C.prototype.getThemeMedia.call(this,e,t,r)}},{key:"getSpecialFeatures",value:function(e,t){return V(t)?Promise.resolve([]):C.prototype.getSpecialFeatures.call(this,e,t)}},{key:"getSimilarItems",value:function(e,t){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getSimilarItems.call(this,e,t)}},{key:"updateFavoriteStatus",value:function(e,t,r){return V(t)?Promise.resolve():C.prototype.updateFavoriteStatus.call(this,e,t,r)}},{key:"getScaledImageUrl",value:function(e,t){if(V(e)||t&&t.itemid&&V(t.itemid)){var r=this.serverInfo(),n=W(e);return this.localAssetManager.getImageUrl(r.Id,n,t)}return C.prototype.getScaledImageUrl.call(this,e,t)}},{key:"reportPlaybackStart",value:function(e){if(!e)throw new Error("null options");return V(e.ItemId)?Promise.resolve():C.prototype.reportPlaybackStart.call(this,e)}},{key:"reportPlaybackProgress",value:function(e){if(!e)throw new Error("null options");if(V(e.ItemId)){var t=this.serverInfo();if(t){var r=this;return this.localAssetManager.getLocalItem(t.Id,W(e.ItemId)).then((function(t){var n=t.Item;return"Video"===n.MediaType||"AudioBook"===n.Type?(n.UserData=n.UserData||{},n.UserData.PlaybackPositionTicks=e.PositionTicks,n.UserData.PlayedPercentage=Math.min(n.RunTimeTicks?(e.PositionTicks||0)/n.RunTimeTicks*100:0,100),r.localAssetManager.addOrUpdateLocalItem(t)):Promise.resolve()}))}return Promise.resolve()}return C.prototype.reportPlaybackProgress.call(this,e)}},{key:"reportPlaybackStopped",value:function(e){if(!e)throw new Error("null options");if(V(e.ItemId)){var t=this.serverInfo(),r={Date:(new Date).getTime(),ItemId:W(e.ItemId),PositionTicks:e.PositionTicks,ServerId:t.Id,Type:0,UserId:this.getCurrentUserId()};return this.localAssetManager.recordUserAction(r)}return C.prototype.reportPlaybackStopped.call(this,e)}},{key:"getIntros",value:function(e){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getIntros.call(this,e)}},{key:"getInstantMixFromItem",value:function(e,t){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getInstantMixFromItem.call(this,e,t)}},{key:"getItemDownloadUrl",value:function(e){if(V(e)){var t=this.serverInfo();if(t)return this.localAssetManager.getLocalItem(t.Id,W(e)).then((function(e){return Promise.resolve(e.LocalPath)}))}return C.prototype.getItemDownloadUrl.call(this,e)}}])&&R(t.prototype,r),n&&R(t,n),o}(C);function Y(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};!1!==t.reportCapabilities&&e.reportCapabilities(s),e.enableAutomaticBitrateDetection=t.enableAutomaticBitrateDetection,!1!==t.enableWebSocket&&(console.log("calling apiClient.ensureWebSocket"),e.ensureWebSocket())}function d(e,t,r){return u._getOrAddApiClient(e,t),(u.onLocalUserSignedIn?u.onLocalUserSignedIn.call(u,r):Promise.resolve()).then((function(){i.trigger(u,"localusersignedin",[r])}))}function h(e,t){return ae({type:"GET",url:ie(t,"System/Info"),dataType:"json",headers:{"X-MediaBrowser-Token":e.AccessToken}}).then((function(t){return ne(e,t),Promise.resolve()}),(function(){return e.UserId=null,e.AccessToken=null,Promise.resolve()}))}function v(e){var t={serverId:(e.serverInfo()||{}).Id};return e.logout().then((function(){i.trigger(u,"localusersignedout",[t])}),(function(){i.trigger(u,"localusersignedout",[t])}))}function f(e){if(e.Address&&e.EndpointAddress){var t=e.EndpointAddress.split(":")[0],r=e.Address.split(":");if(r.length>1){var n=r[r.length-1];isNaN(parseInt(n))||(t+=":".concat(n))}return ue(t)}return null}function g(e){var t=[],r=[];return!e.manualAddressOnly&&e.LocalAddress&&-1===r.indexOf(e.LocalAddress)&&(t.push({url:e.LocalAddress,mode:Z,timeout:0}),r.push(t[t.length-1].url)),e.ManualAddress&&-1===r.indexOf(e.ManualAddress)&&(t.push({url:e.ManualAddress,mode:ee,timeout:100}),r.push(t[t.length-1].url)),!e.manualAddressOnly&&e.RemoteAddress&&-1===r.indexOf(e.RemoteAddress)&&(t.push({url:e.RemoteAddress,mode:$,timeout:200}),r.push(t[t.length-1].url)),console.log("tryReconnect: "+r.join("|")),new Promise((function(e,r){var n={};n.numAddresses=t.length,n.rejects=0,t.map((function(t){setTimeout((function(){n.resolved||function(e,t,r,n,i){console.log("getTryConnectPromise "+e),ae({url:ie(e,"system/info/public"),timeout:2e4,type:"GET",dataType:"json"}).then((function(i){r.resolved||(r.resolved=!0,console.log("Reconnect succeeded to "+e),n({url:e,connectionMode:t,data:i}))}),(function(){console.log("Reconnect failed to "+e),r.resolved||(r.rejects++,r.rejects>=r.numAddresses&&i())}))}(t.url,t.mode,n,e,r)}),t.timeout)}))}))}function p(e,t){var r={ManualAddress:e,LastConnectionMode:ee};return u.connectToServer(r,t).then((function(e){return"Unavailable"===e.State?Promise.reject():e}))}this._apiClients=[],u._minServerVersion="3.2.33",u.appVersion=function(){return n},u.appName=function(){return r},u.capabilities=function(){return s},u.deviceId=function(){return a},u.credentialProvider=function(){return t},u.getServerInfo=function(e){return t.credentials().Servers.filter((function(t){return t.Id===e}))[0]},u.getLastUsedServer=function(){var e=t.credentials().Servers;return e.sort((function(e,t){return(t.DateLastAccessed||0)-(e.DateLastAccessed||0)})),e.length?e[0]:null},u.addApiClient=function(e){u._apiClients.push(e);var r=t.credentials().Servers.filter((function(t){return le(t.ManualAddress,e.serverAddress())||le(t.LocalAddress,e.serverAddress())||le(t.RemoteAddress,e.serverAddress())})),n=r.length?r[0]:e.serverInfo();if(n.DateLastAccessed=(new Date).getTime(),n.LastConnectionMode=ee,n.ManualAddress=e.serverAddress(),e.manualAddressOnly&&(n.manualAddressOnly=!0),e.serverInfo(n),e.onAuthenticated=function(e,t){return l(e,t,{},!0)},!r.length){var o=t.credentials();o.Servers=[n],t.credentials(o)}i.trigger(u,"apiclientcreated",[e])},u.clearData=function(){console.log("connection manager clearing data");var e=t.credentials();e.Servers=[],t.credentials(e)},u._getOrAddApiClient=function(e,t){var s=u.getApiClient(e.Id);return s||(s=new C(t,r,n,o,a),u._apiClients.push(s),s.serverInfo(e),s.onAuthenticated=function(e,t){return l(e,t,{},!0)},i.trigger(u,"apiclientcreated",[s])),console.log("returning instance from getOrAddApiClient"),s},u.getOrCreateApiClient=function(e){var r=t.credentials().Servers.filter((function(t){return le(t.Id,e)}));if(!r.length)throw new Error("Server not found: ".concat(e));var n=r[0];return u._getOrAddApiClient(n,te(n,n.LastConnectionMode))},u.user=function(e){return new Promise((function(t,r){var n;e&&e.getCurrentUserId()&&e&&e.getCurrentUserId()&&e.getCurrentUser().then((function(e){var r=function(e){return e&&e.PrimaryImageTag?{url:u.getApiClient(e).getUserImageUrl(e.Id,{tag:e.PrimaryImageTag,type:"Primary"}),supportsParams:!0}:{url:null,supportsParams:!1}}(n=e);t({localUser:n,name:n?n.Name:null,imageUrl:r.url,supportsImageParams:r.supportsParams})}))}))},u.logout=function(){for(var e=[],r=0,n=u._apiClients.length;ro)return 1}return 0}(u.minServerVersion(),o.Version)?(console.log("minServerVersion requirement not met. Server version: "+o.Version),n({State:"ServerUpdateNeeded",Servers:[e]})):e.Id&&o.Id!==e.Id?(console.log("http request succeeded, but found a different server Id than what was expected"),re(0,n)):function e(r,n,o,a,s,l){var v=arguments.length>6&&void 0!==arguments[6]?arguments[6]:{},f=t.credentials();if(!1===v.enableAutoLogin)r.UserId=null,r.AccessToken=null;else if(r.AccessToken&&s)return void h(r,a).then((function(){e(r,n,o,a,!1,l,v)}));ne(r,n),r.LastConnectionMode=o,!1!==v.updateDateLastAccessed&&(r.DateLastAccessed=(new Date).getTime());t.addOrUpdateServer(f.Servers,r),t.credentials(f);var g={Servers:[]};g.ApiClient=u._getOrAddApiClient(r,a),g.ApiClient.setSystemInfo(n),g.State=r.AccessToken&&!1!==v.enableAutoLogin?"SignedIn":"ServerSignIn",g.Servers.push(r),g.ApiClient.enableAutomaticBitrateDetection=v.enableAutomaticBitrateDetection,g.ApiClient.updateServerInfo(r,a);var p=function(){l(g),i.trigger(u,"connected",[g])};"SignedIn"===g.State?(c(g.ApiClient,v),g.ApiClient.getCurrentUser().then((function(e){d(r,a,e).then(p,p)}),p)):p()}(e,o,s,a,!0,n,r)}),(function(){re(0,n)}))}))},u.connectToAddress=function(e,t){if(!e)return Promise.reject();e=ue(e);var r=[];/^[^:]+:\/\//.test(e)?r.push(e):(r.push("https://".concat(e)),r.push("http://".concat(e)));var n=0;return p(r[n],t).catch((function e(){return console.log("connectToAddress ".concat(r[n]," failed")),++n=r.length)return j(t,i);var o=r[n];return t.getDownloadSpeed(o.bytes).then((function(i){return i=20?Promise.reject():function(e){var t=[],r=[],n=e.serverInfo();return n.LocalAddress&&-1===r.indexOf(n.LocalAddress)&&(t.push({url:n.LocalAddress,timeout:0}),r.push(t[t.length-1].url)),n.ManualAddress&&-1===r.indexOf(n.ManualAddress)&&(t.push({url:n.ManualAddress,timeout:100}),r.push(t[t.length-1].url)),n.RemoteAddress&&-1===r.indexOf(n.RemoteAddress)&&(t.push({url:n.RemoteAddress,timeout:200}),r.push(t[t.length-1].url)),console.log("tryReconnect: "+r.join("|")),new Promise((function(r,n){var i={};i.numAddresses=t.length,i.rejects=0,t.map((function(t){setTimeout((function(){i.resolved||function(e,t,r,n,i){console.log("getTryConnectPromise "+t),I(e.getUrl("system/info/public",null,t),{method:"GET",accept:"application/json"},15e3).then((function(){r.resolved||(r.resolved=!0,console.log("Reconnect succeeded to "+t),e.serverAddress(t),n())}),(function(){r.resolved||(console.log("Reconnect failed to "+t),r.rejects++,r.rejects>=r.numAddresses&&i())}))}(e,t.url,i,r,n)}),t.timeout)}))}))}(t).catch((function(n){return console.log("error in tryReconnectInternal: "+(n||"")),new Promise((function(n,i){setTimeout((function(){e(t,r+1).then(n,i)}),500)}))}))}(r).then((function(){return console.log("Reconnect succeesed"),e.url=e.url.replace(i,r.serverAddress()),r.fetchWithFailover(e,!1)})).catch((function(t){throw console.log("Reconnect failed"),y(r,e.url,{}),t}))}))}},{key:"fetch",value:function(e,t){if(!e)return Promise.reject("Request cannot be null");if(e.headers=e.headers||{},!1!==t&&this.setRequestHeaders(e.headers),!1===this.enableAutomaticNetworking||"GET"!==e.type){console.log("Requesting url without automatic networking: ".concat(e.url));var r=this;return S(e).then((function(t){return r.lastFetch=(new Date).getTime(),t.status<400?"json"===e.dataType||"application/json"===e.headers.accept?t.json():"text"===e.dataType||0===(t.headers.get("Content-Type")||"").toLowerCase().indexOf("text/")?t.text():t:(y(r,e.url,t),Promise.reject(t))})).catch((function(t){return y(r,e.url,{}),Promise.reject(t)}))}return this.fetchWithFailover(e,!0)}},{key:"setAuthenticationInfo",value:function(e,t){this._currentUser=null,this._serverInfo.AccessToken=e,this._serverInfo.UserId=t,v(this)}},{key:"serverInfo",value:function(e){return e&&(this._serverInfo=e),this._serverInfo}},{key:"getCurrentUserId",value:function(){return this._serverInfo.UserId}},{key:"accessToken",value:function(){return this._serverInfo.AccessToken}},{key:"serverId",value:function(){return this.serverInfo().Id}},{key:"serverName",value:function(){return this.serverInfo().Name}},{key:"ajax",value:function(e,t){return e?this.fetch(e,t):Promise.reject("Request cannot be null")}},{key:"getCurrentUser",value:function(e){if(this._currentUser)return Promise.resolve(this._currentUser);var t=this.getCurrentUserId();if(!t)return Promise.reject();var r,n=this,i=this.getUser(t).then((function(e){return l.setItem("user-".concat(e.Id,"-").concat(e.ServerId),JSON.stringify(e)),n._currentUser=e,e})).catch((function(e){if(!e.status&&t&&n.accessToken()&&(r=T(n,t)))return Promise.resolve(r);throw e}));return!this.lastFetch&&!1!==e&&(r=T(n,t))?Promise.resolve(r):i}},{key:"isLoggedIn",value:function(){var e=this.serverInfo();return!!(e&&e.UserId&&e.AccessToken)}},{key:"logout",value:function(){var e=this;g(this),this.closeWebSocket();var t=function(){var t=e.serverInfo();t&&t.UserId&&t.Id&&l.removeItem("user-".concat(t.UserId,"-").concat(t.Id)),e.setAuthenticationInfo(null,null)};if(this.accessToken()){var r=this.getUrl("Sessions/Logout");return this.ajax({type:"POST",url:r}).then(t,t)}return t(),Promise.resolve()}},{key:"authenticateUserByName",value:function(e,t){var r=this;if(!e)return Promise.reject();var n=this.getUrl("Users/authenticatebyname");return new Promise((function(i,o){var a={Username:e,Pw:t||""};r.ajax({type:"POST",url:n,data:JSON.stringify(a),dataType:"json",contentType:"application/json"}).then((function(e){var t=function(){v(r),i(e)};r.onAuthenticated?r.onAuthenticated(r,e).then(t):t()})).catch(o)}))}},{key:"ensureWebSocket",value:function(){if(!this.isWebSocketOpenOrConnecting()&&this.isWebSocketSupported())try{this.openWebSocket()}catch(e){console.log("Error opening web socket: ".concat(e))}}},{key:"openWebSocket",value:function(){var e=this.accessToken();if(!e)throw new Error("Cannot open web socket without access token.");var t=this.getUrl("socket");t=p(t,"emby/socket","embywebsocket"),t=p(t,"https:","wss:"),t=p(t,"http:","ws:"),t+="?api_key=".concat(e),t+="&deviceId=".concat(this.deviceId()),console.log("opening web socket with url: ".concat(t));var r,n,o=new WebSocket(t);o.onmessage=P.bind(this),o.onopen=E.bind(this),o.onerror=A.bind(this),r=this,(n=o).onclose=function(){console.log("web socket closed"),O(n),r._webSocket===n&&(console.log("nulling out web socket"),r._webSocket=null),setTimeout((function(){i.trigger(r,"websocketclose")}),0)},this._webSocket=o}},{key:"closeWebSocket",value:function(){var e=this._webSocket;e&&e.readyState===WebSocket.OPEN&&e.close()}},{key:"sendWebSocketMessage",value:function(e,t){console.log("Sending web socket message: ".concat(e));var r={MessageType:e};t&&(r.Data=t),r=JSON.stringify(r),this._webSocket.send(r)}},{key:"sendMessage",value:function(e,t){this.isWebSocketOpen()&&this.sendWebSocketMessage(e,t)}},{key:"isMessageChannelOpen",value:function(){return this.isWebSocketOpen()}},{key:"isWebSocketOpen",value:function(){var e=this._webSocket;return!!e&&e.readyState===WebSocket.OPEN}},{key:"isWebSocketOpenOrConnecting",value:function(){var e=this._webSocket;return!!e&&(e.readyState===WebSocket.OPEN||e.readyState===WebSocket.CONNECTING)}},{key:"get",value:function(e){return this.ajax({type:"GET",url:e})}},{key:"getJSON",value:function(e,t){return this.fetch({url:e,type:"GET",dataType:"json",headers:{accept:"application/json"}},t)}},{key:"updateServerInfo",value:function(e,t){if(null==e)throw new Error("server cannot be null");if(this.serverInfo(e),!t)throw new Error("serverUrl cannot be null. serverInfo: ".concat(JSON.stringify(e)));console.log("Setting server address to ".concat(t)),this.serverAddress(t)}},{key:"isWebSocketSupported",value:function(){try{return null!=WebSocket}catch(e){return!1}}},{key:"clearAuthenticationInfo",value:function(){this.setAuthenticationInfo(null,null)}},{key:"encodeName",value:function(e){var t=m({name:e=(e=(e=e.split("/").join("-")).split("&").join("-")).split("?").join("-")});return t.substring(t.indexOf("=")+1).replace("'","%27")}},{key:"getServerTime",value:function(){var e=this.getUrl("GetUTCTime");return this.ajax({type:"GET",url:e})}},{key:"getDownloadSpeed",value:function(e){var t=this.getUrl("Playback/BitrateTest",{Size:e}),r=(new Date).getTime();return this.ajax({type:"GET",url:t,timeout:5e3}).then((function(){var t=((new Date).getTime()-r)/1e3,n=e/t;return Math.round(8*n)}))}},{key:"detectBitrate",value:function(e){if(!e&&this.lastDetectedBitrate&&(new Date).getTime()-(this.lastDetectedBitrateTime||0)<=36e5)return Promise.resolve(this.lastDetectedBitrate);var t=this;return this.getEndpointInfo().then((function(e){return N(t,e)}),(function(e){return N(t,{})}))}},{key:"getItem",value:function(e,t){if(!t)throw new Error("null itemId");var r=e?this.getUrl("Users/".concat(e,"/Items/").concat(t)):this.getUrl("Items/".concat(t));return this.getJSON(r)}},{key:"getRootFolder",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/Items/Root"));return this.getJSON(t)}},{key:"getNotificationSummary",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Notifications/".concat(e,"/Summary"));return this.getJSON(t)}},{key:"getNotifications",value:function(e,t){if(!e)throw new Error("null userId");var r=this.getUrl("Notifications/".concat(e),t||{});return this.getJSON(r)}},{key:"markNotificationsRead",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null idList");var n=r?"Read":"Unread",i={UserId:e,Ids:t.join(",")},o=this.getUrl("Notifications/".concat(e,"/").concat(n),i);return this.ajax({type:"POST",url:o})}},{key:"getRemoteImageProviders",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages/Providers"),e);return this.getJSON(r)}},{key:"getAvailableRemoteImages",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages"),e);return this.getJSON(r)}},{key:"downloadRemoteImage",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages/Download"),e);return this.ajax({type:"POST",url:r})}},{key:"getRecordingFolders",value:function(e){var t=this.getUrl("LiveTv/Recordings/Folders",{userId:e});return this.getJSON(t)}},{key:"getLiveTvInfo",value:function(e){var t=this.getUrl("LiveTv/Info",e||{});return this.getJSON(t)}},{key:"getLiveTvGuideInfo",value:function(e){var t=this.getUrl("LiveTv/GuideInfo",e||{});return this.getJSON(t)}},{key:"getLiveTvChannel",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Channels/".concat(e),r);return this.getJSON(n)}},{key:"getLiveTvChannels",value:function(e){var t=this.getUrl("LiveTv/Channels",e||{});return this.getJSON(t)}},{key:"getLiveTvPrograms",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return e.channelIds&&e.channelIds.length>1800?this.ajax({type:"POST",url:this.getUrl("LiveTv/Programs"),data:JSON.stringify(e),contentType:"application/json",dataType:"json"}):this.ajax({type:"GET",url:this.getUrl("LiveTv/Programs",e),dataType:"json"})}},{key:"getLiveTvRecommendedPrograms",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.ajax({type:"GET",url:this.getUrl("LiveTv/Programs/Recommended",e),dataType:"json"})}},{key:"getLiveTvRecordings",value:function(e){var t=this.getUrl("LiveTv/Recordings",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingSeries",value:function(e){var t=this.getUrl("LiveTv/Recordings/Series",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingGroups",value:function(e){var t=this.getUrl("LiveTv/Recordings/Groups",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingGroup",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Recordings/Groups/".concat(e));return this.getJSON(t)}},{key:"getLiveTvRecording",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Recordings/".concat(e),r);return this.getJSON(n)}},{key:"getLiveTvProgram",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Programs/".concat(e),r);return this.getJSON(n)}},{key:"deleteLiveTvRecording",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Recordings/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"cancelLiveTvTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Timers/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"getLiveTvTimers",value:function(e){var t=this.getUrl("LiveTv/Timers",e||{});return this.getJSON(t)}},{key:"getLiveTvTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Timers/".concat(e));return this.getJSON(t)}},{key:"getNewLiveTvTimerDefaults",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("LiveTv/Timers/Defaults",e);return this.getJSON(t)}},{key:"createLiveTvTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/Timers");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateLiveTvTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/Timers/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"resetLiveTvTuner",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Tuners/".concat(e,"/Reset"));return this.ajax({type:"POST",url:t})}},{key:"getLiveTvSeriesTimers",value:function(e){var t=this.getUrl("LiveTv/SeriesTimers",e||{});return this.getJSON(t)}},{key:"getLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e));return this.getJSON(t)}},{key:"cancelLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"createLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/SeriesTimers");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"getRegistrationInfo",value:function(e){var t=this.getUrl("Registrations/".concat(e));return this.getJSON(t)}},{key:"getSystemInfo",value:function(e){var t=this.getUrl("System/Info"),r=this;return this.getJSON(t).then((function(e){return r.setSystemInfo(e),Promise.resolve(e)}))}},{key:"getSyncStatus",value:function(){var e=this.getUrl("Sync/"+itemId+"/Status");return this.ajax({url:e,type:"POST",dataType:"json",contentType:"application/json",data:JSON.stringify({TargetId:this.deviceId()})})}},{key:"getPublicSystemInfo",value:function(){var e=this.getUrl("System/Info/Public"),t=this;return this.getJSON(e).then((function(e){return t.setSystemInfo(e),Promise.resolve(e)}))}},{key:"getInstantMixFromItem",value:function(e,t){var r=this.getUrl("Items/".concat(e,"/InstantMix"),t);return this.getJSON(r)}},{key:"getEpisodes",value:function(e,t){var r=this.getUrl("Shows/".concat(e,"/Episodes"),t);return this.getJSON(r)}},{key:"getDisplayPreferences",value:function(e,t,r){var n=this.getUrl("DisplayPreferences/".concat(e),{userId:t,client:r});return this.getJSON(n)}},{key:"updateDisplayPreferences",value:function(e,t,r,n){var i=this.getUrl("DisplayPreferences/".concat(e),{userId:r,client:n});return this.ajax({type:"POST",url:i,data:JSON.stringify(t),contentType:"application/json"})}},{key:"getSeasons",value:function(e,t){var r=this.getUrl("Shows/".concat(e,"/Seasons"),t);return this.getJSON(r)}},{key:"getSimilarItems",value:function(e,t){var r=this.getUrl("Items/".concat(e,"/Similar"),t);return this.getJSON(r)}},{key:"getCultures",value:function(){var e=this.getUrl("Localization/cultures");return this.getJSON(e)}},{key:"getCountries",value:function(){var e=this.getUrl("Localization/countries");return this.getJSON(e)}},{key:"getPlaybackInfo",value:function(e,t,r){var n={DeviceProfile:r};return this.ajax({url:this.getUrl("Items/".concat(e,"/PlaybackInfo"),t),type:"POST",data:JSON.stringify(n),contentType:"application/json",dataType:"json"})}},{key:"getLiveStreamMediaInfo",value:function(e){var t={LiveStreamId:e};return this.ajax({url:this.getUrl("LiveStreams/MediaInfo"),type:"POST",data:JSON.stringify(t),contentType:"application/json",dataType:"json"})}},{key:"getIntros",value:function(e){return this.getJSON(this.getUrl("Users/".concat(this.getCurrentUserId(),"/Items/").concat(e,"/Intros")))}},{key:"getDirectoryContents",value:function(e,t){if(!e)throw new Error("null path");if("string"!=typeof e)throw new Error("invalid path");(t=t||{}).path=e;var r=this.getUrl("Environment/DirectoryContents",t);return this.getJSON(r)}},{key:"getNetworkShares",value:function(e){if(!e)throw new Error("null path");var t={};t.path=e;var r=this.getUrl("Environment/NetworkShares",t);return this.getJSON(r)}},{key:"getParentPath",value:function(e){if(!e)throw new Error("null path");var t={};t.path=e;var r=this.getUrl("Environment/ParentPath",t);return this.ajax({type:"GET",url:r,dataType:"text"})}},{key:"getDrives",value:function(){var e=this.getUrl("Environment/Drives");return this.getJSON(e)}},{key:"getNetworkDevices",value:function(){var e=this.getUrl("Environment/NetworkDevices");return this.getJSON(e)}},{key:"cancelPackageInstallation",value:function(e){if(!e)throw new Error("null installationId");var t=this.getUrl("Packages/Installing/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"refreshItem",value:function(e,t){if(!e)throw new Error("null itemId");var r=this.getUrl("Items/".concat(e,"/Refresh"),t||{});return this.ajax({type:"POST",url:r})}},{key:"installPlugin",value:function(e,t,r){if(!e)throw new Error("null name");var n={AssemblyGuid:t};r&&(n.version=r);var i=this.getUrl("Packages/Installed/".concat(e),n);return this.ajax({type:"POST",url:i})}},{key:"restartServer",value:function(){var e=this.getUrl("System/Restart");return this.ajax({type:"POST",url:e})}},{key:"shutdownServer",value:function(){var e=this.getUrl("System/Shutdown");return this.ajax({type:"POST",url:e})}},{key:"getPackageInfo",value:function(e,t){if(!e)throw new Error("null name");var r={AssemblyGuid:t},n=this.getUrl("Packages/".concat(e),r);return this.getJSON(n)}},{key:"getVirtualFolders",value:function(){var e="Library/VirtualFolders";return e=this.getUrl(e),this.getJSON(e)}},{key:"getPhysicalPaths",value:function(){var e=this.getUrl("Library/PhysicalPaths");return this.getJSON(e)}},{key:"getServerConfiguration",value:function(){var e=this.getUrl("System/Configuration");return this.getJSON(e)}},{key:"getDevicesOptions",value:function(){var e=this.getUrl("System/Configuration/devices");return this.getJSON(e)}},{key:"getContentUploadHistory",value:function(){var e=this.getUrl("Devices/CameraUploads",{DeviceId:this.deviceId()});return this.getJSON(e)}},{key:"getNamedConfiguration",value:function(e){var t=this.getUrl("System/Configuration/".concat(e));return this.getJSON(t)}},{key:"getScheduledTasks",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("ScheduledTasks",e);return this.getJSON(t)}},{key:"startScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/Running/".concat(e));return this.ajax({type:"POST",url:t})}},{key:"getScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/".concat(e));return this.getJSON(t)}},{key:"getNextUpEpisodes",value:function(e){var t=this.getUrl("Shows/NextUp",e);return this.getJSON(t)}},{key:"stopScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/Running/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"getPluginConfiguration",value:function(e){if(!e)throw new Error("null Id");var t=this.getUrl("Plugins/".concat(e,"/Configuration"));return this.getJSON(t)}},{key:"getAvailablePlugins",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};e.PackageType="UserInstalled";var t=this.getUrl("Packages",e);return this.getJSON(t)}},{key:"uninstallPlugin",value:function(e){if(!e)throw new Error("null Id");var t=this.getUrl("Plugins/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"removeVirtualFolder",value:function(e,t){if(!e)throw new Error("null name");var r="Library/VirtualFolders";return r=this.getUrl(r,{refreshLibrary:!!t,name:e}),this.ajax({type:"DELETE",url:r})}},{key:"addVirtualFolder",value:function(e,t,r,n){if(!e)throw new Error("null name");var i={};t&&(i.collectionType=t),i.refreshLibrary=!!r,i.name=e;var o="Library/VirtualFolders";return o=this.getUrl(o,i),this.ajax({type:"POST",url:o,data:JSON.stringify({LibraryOptions:n}),contentType:"application/json"})}},{key:"updateVirtualFolderOptions",value:function(e,t){if(!e)throw new Error("null name");var r="Library/VirtualFolders/LibraryOptions";return r=this.getUrl(r),this.ajax({type:"POST",url:r,data:JSON.stringify({Id:e,LibraryOptions:t}),contentType:"application/json"})}},{key:"renameVirtualFolder",value:function(e,t,r){if(!e)throw new Error("null name");var n="Library/VirtualFolders/Name";return n=this.getUrl(n,{refreshLibrary:!!r,newName:t,name:e}),this.ajax({type:"POST",url:n})}},{key:"addMediaPath",value:function(e,t,r,n){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null mediaPath");var i="Library/VirtualFolders/Paths",o={Path:t};return r&&(o.NetworkPath=r),i=this.getUrl(i,{refreshLibrary:!!n}),this.ajax({type:"POST",url:i,data:JSON.stringify({Name:e,PathInfo:o}),contentType:"application/json"})}},{key:"updateMediaPath",value:function(e,t){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null pathInfo");var r="Library/VirtualFolders/Paths/Update";return r=this.getUrl(r),this.ajax({type:"POST",url:r,data:JSON.stringify({Name:e,PathInfo:t}),contentType:"application/json"})}},{key:"removeMediaPath",value:function(e,t,r){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null mediaPath");var n="Library/VirtualFolders/Paths";return n=this.getUrl(n,{refreshLibrary:!!r,path:t,name:e}),this.ajax({type:"DELETE",url:n})}},{key:"deleteUser",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("Users/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"deleteUserImage",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null imageType");var n=this.getUrl("Users/".concat(e,"/Images/").concat(t));return null!=r&&(n+="/".concat(r)),this.ajax({type:"DELETE",url:n})}},{key:"deleteItemImage",value:function(e,t,r){if(!t)throw new Error("null imageType");var n=this.getUrl("Items/".concat(e,"/Images"));return n+="/".concat(t),null!=r&&(n+="/".concat(r)),this.ajax({type:"DELETE",url:n})}},{key:"deleteItem",value:function(e){if(!e)throw new Error("null itemId");var t=this.getUrl("Items/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"stopActiveEncodings",value:function(e){var t={deviceId:this.deviceId()};e&&(t.PlaySessionId=e);var r=this.getUrl("Videos/ActiveEncodings",t);return this.ajax({type:"DELETE",url:r})}},{key:"reportCapabilities",value:function(e){var t=this.getUrl("Sessions/Capabilities/Full");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateItemImageIndex",value:function(e,t,r,n){if(!t)throw new Error("null imageType");var i={newIndex:n},o=this.getUrl("Items/".concat(e,"/Images/").concat(t,"/").concat(r,"/Index"),i);return this.ajax({type:"POST",url:o})}},{key:"getItemImageInfos",value:function(e){var t=this.getUrl("Items/".concat(e,"/Images"));return this.getJSON(t)}},{key:"getCriticReviews",value:function(e,t){if(!e)throw new Error("null itemId");var r=this.getUrl("Items/".concat(e,"/CriticReviews"),t);return this.getJSON(r)}},{key:"getItemDownloadUrl",value:function(e){if(!e)throw new Error("itemId cannot be empty");var t="Items/".concat(e,"/Download");return this.getUrl(t,{api_key:this.accessToken()})}},{key:"getSessions",value:function(e){var t=this.getUrl("Sessions",e);return this.getJSON(t)}},{key:"uploadUserImage",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null imageType");if(!r)throw new Error("File must be an image.");if(!r.type.startsWith("image/"))throw new Error("File must be an image.");var n=this;return new Promise((function(i,o){var a=new FileReader;a.onerror=function(){o()},a.onabort=function(){o()},a.onload=function(a){var s=a.target.result.split(",")[1],u=n.getUrl("Users/".concat(e,"/Images/").concat(t));n.ajax({type:"POST",url:u,data:s,contentType:"image/".concat(r.name.substring(r.name.lastIndexOf(".")+1))}).then(i,o)},a.readAsDataURL(r)}))}},{key:"uploadItemImage",value:function(e,t,r){if(!e)throw new Error("null itemId");if(!t)throw new Error("null imageType");if(!r)throw new Error("File must be an image.");if(!r.type.startsWith("image/"))throw new Error("File must be an image.");var n=this.getUrl("Items/".concat(e,"/Images"));n+="/".concat(t);var i=this;return new Promise((function(e,t){var o=new FileReader;o.onerror=function(){t()},o.onabort=function(){t()},o.onload=function(o){var a=o.target.result.split(",")[1];i.ajax({type:"POST",url:n,data:a,contentType:"image/".concat(r.name.substring(r.name.lastIndexOf(".")+1))}).then(e,t)},o.readAsDataURL(r)}))}},{key:"getInstalledPlugins",value:function(){var e=this.getUrl("Plugins",{});return this.getJSON(e)}},{key:"getUser",value:function(e){if(!e)throw new Error("Must supply a userId");var t=this.getUrl("Users/".concat(e));return this.getJSON(t)}},{key:"getStudio",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Studios/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getGenre",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Genres/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getMusicGenre",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("MusicGenres/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getArtist",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Artists/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getPerson",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Persons/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getPublicUsers",value:function(){var e=this.getUrl("users/public");return this.ajax({type:"GET",url:e,dataType:"json"},!1)}},{key:"getUsers",value:function(e){var t=this.getUrl("users",e||{});return this.getJSON(t)}},{key:"getParentalRatings",value:function(){var e=this.getUrl("Localization/ParentalRatings");return this.getJSON(e)}},{key:"getDefaultImageQuality",value:function(e){return"backdrop"===e.toLowerCase()?80:90}},{key:"getUserImageUrl",value:function(e,t){if(!e)throw new Error("null userId");t=t||{};var r="Users/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),x(this,t),delete t.type,delete t.index,this.getUrl(r,t)}},{key:"getImageUrl",value:function(e,t){if(!e)throw new Error("itemId cannot be empty");t=t||{};var r="Items/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),t.quality=t.quality||this.getDefaultImageQuality(t.type),this.normalizeImageOptions&&this.normalizeImageOptions(t),delete t.type,delete t.index,this.getUrl(r,t)}},{key:"getScaledImageUrl",value:function(e,t){if(!e)throw new Error("itemId cannot be empty");t=t||{};var r="Items/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),x(this,t),delete t.type,delete t.index,delete t.minScale,this.getUrl(r,t)}},{key:"getThumbImageUrl",value:function(e,t){if(!e)throw new Error("null item");return(t=t||{}).imageType="thumb",e.ImageTags&&e.ImageTags.Thumb?(t.tag=e.ImageTags.Thumb,this.getImageUrl(e.Id,t)):e.ParentThumbItemId?(t.tag=e.ImageTags.ParentThumbImageTag,this.getImageUrl(e.ParentThumbItemId,t)):null}},{key:"updateUserPassword",value:function(e,t,r){if(!e)return Promise.reject();var n=this.getUrl("Users/".concat(e,"/Password"));return this.ajax({type:"POST",url:n,data:JSON.stringify({CurrentPw:t||"",NewPw:r}),contentType:"application/json"})}},{key:"updateEasyPassword",value:function(e,t){if(e){var r=this.getUrl("Users/".concat(e,"/EasyPassword"));return this.ajax({type:"POST",url:r,data:{NewPw:t}})}Promise.reject()}},{key:"resetUserPassword",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/Password")),r={resetPassword:!0};return this.ajax({type:"POST",url:t,data:r})}},{key:"resetEasyPassword",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/EasyPassword")),r={resetPassword:!0};return this.ajax({type:"POST",url:t,data:r})}},{key:"updateServerConfiguration",value:function(e){if(!e)throw new Error("null configuration");var t=this.getUrl("System/Configuration");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateNamedConfiguration",value:function(e,t){if(!t)throw new Error("null configuration");var r=this.getUrl("System/Configuration/".concat(e));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateItem",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("Items/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updatePluginSecurityInfo",value:function(e){var t=this.getUrl("Plugins/SecurityInfo");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"createUser",value:function(e){var t=this.getUrl("Users/New");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json",headers:{accept:"application/json"}})}},{key:"updateUser",value:function(e){if(!e)throw new Error("null user");var t=this.getUrl("Users/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateUserPolicy",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null policy");var r=this.getUrl("Users/".concat(e,"/Policy"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateUserConfiguration",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null configuration");var r=this.getUrl("Users/".concat(e,"/Configuration"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateScheduledTaskTriggers",value:function(e,t){if(!e)throw new Error("null id");if(!t)throw new Error("null triggers");var r=this.getUrl("ScheduledTasks/".concat(e,"/Triggers"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updatePluginConfiguration",value:function(e,t){if(!e)throw new Error("null Id");if(!t)throw new Error("null configuration");var r=this.getUrl("Plugins/".concat(e,"/Configuration"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"getAncestorItems",value:function(e,t){if(!e)throw new Error("null itemId");var r={};t&&(r.userId=t);var n=this.getUrl("Items/".concat(e,"/Ancestors"),r);return this.getJSON(n)}},{key:"getItems",value:function(e,t){var r;return r="string"===c(e).toString().toLowerCase()?this.getUrl("Users/".concat(e,"/Items"),t):this.getUrl("Items",t),this.getJSON(r)}},{key:"getResumableItems",value:function(e,t){return this.isMinServerVersion("3.2.33")?this.getJSON(this.getUrl("Users/".concat(e,"/Items/Resume"),t)):this.getItems(e,Object.assign({SortBy:"DatePlayed",SortOrder:"Descending",Filters:"IsResumable",Recursive:!0,CollapseBoxSetItems:!1,ExcludeLocationTypes:"Virtual"},t))}},{key:"getMovieRecommendations",value:function(e){return this.getJSON(this.getUrl("Movies/Recommendations",e))}},{key:"getUpcomingEpisodes",value:function(e){return this.getJSON(this.getUrl("Shows/Upcoming",e))}},{key:"getUserViews",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0,r=this.getUrl("Users/".concat(t||this.getCurrentUserId(),"/Views"),e);return this.getJSON(r)}},{key:"getArtists",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Artists",t);return this.getJSON(r)}},{key:"getAlbumArtists",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Artists/AlbumArtists",t);return this.getJSON(r)}},{key:"getGenres",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Genres",t);return this.getJSON(r)}},{key:"getMusicGenres",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("MusicGenres",t);return this.getJSON(r)}},{key:"getPeople",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Persons",t);return this.getJSON(r)}},{key:"getStudios",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Studios",t);return this.getJSON(r)}},{key:"getLocalTrailers",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/LocalTrailers"));return this.getJSON(r)}},{key:"getAdditionalVideoParts",value:function(e,t){if(!t)throw new Error("null itemId");var r={};e&&(r.userId=e);var n=this.getUrl("Videos/".concat(t,"/AdditionalParts"),r);return this.getJSON(n)}},{key:"getThemeMedia",value:function(e,t,r){if(!t)throw new Error("null itemId");var n={};e&&(n.userId=e),n.InheritFromParent=r||!1;var i=this.getUrl("Items/".concat(t,"/ThemeMedia"),n);return this.getJSON(i)}},{key:"getSearchHints",value:function(e){var t=this.getUrl("Search/Hints",e),r=this.serverId();return this.getJSON(t).then((function(e){return e.SearchHints.forEach((function(e){e.ServerId=r})),e}))}},{key:"getSpecialFeatures",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/SpecialFeatures"));return this.getJSON(r)}},{key:"getDateParamValue",value:function(e){function t(e){return e<10?"0".concat(e):e}var r=e;return"".concat(r.getFullYear()).concat(t(r.getMonth()+1)).concat(t(r.getDate())).concat(t(r.getHours())).concat(t(r.getMinutes())).concat(t(r.getSeconds()))}},{key:"markPlayed",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n={};r&&(n.DatePlayed=this.getDateParamValue(r));var i=this.getUrl("Users/".concat(e,"/PlayedItems/").concat(t),n);return this.ajax({type:"POST",url:i,dataType:"json"})}},{key:"markUnplayed",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/PlayedItems/").concat(t));return this.ajax({type:"DELETE",url:r,dataType:"json"})}},{key:"updateFavoriteStatus",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n=this.getUrl("Users/".concat(e,"/FavoriteItems/").concat(t)),i=r?"POST":"DELETE";return this.ajax({type:i,url:n,dataType:"json"})}},{key:"updateUserItemRating",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/Rating"),{likes:r});return this.ajax({type:"POST",url:n,dataType:"json"})}},{key:"getItemCounts",value:function(e){var t={};e&&(t.userId=e);var r=this.getUrl("Items/Counts",t);return this.getJSON(r)}},{key:"clearUserItemRating",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/Rating"));return this.ajax({type:"DELETE",url:r,dataType:"json"})}},{key:"reportPlaybackStart",value:function(e){if(!e)throw new Error("null options");this.lastPlaybackProgressReport=0,this.lastPlaybackProgressReportTicks=null,g(this),w(this);var t=this.getUrl("Sessions/Playing");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"reportPlaybackProgress",value:function(e){if(!e)throw new Error("null options");var t=e.EventName||"timeupdate",r=h[t]||0,n=(new Date).getTime()-(this.lastPlaybackProgressReport||0),i=e.PositionTicks;if(n=5e7&&(r=0)}if(r<(void 0!==this.reportPlaybackProgressTimeout?this.reportPlaybackProgressTimeout:1e6)&&w(this),this.lastPlaybackProgressOptions=e,this.reportPlaybackProgressPromise)return Promise.resolve();var a,s=this,u=!1,l=function(){s.reportPlaybackProgressPromise===a&&(delete s.lastPlaybackProgressOptions,delete s.reportPlaybackProgressTimeout,delete s.reportPlaybackProgressPromise,delete s.reportPlaybackProgressCancel)},c=Math.max(0,r-n);return a=new Promise((function(e,t){return setTimeout(e,c)})).then((function(){return u?Promise.resolve():function(e){if(l(),!e)throw new Error("null options");s.lastPlaybackProgressReport=(new Date).getTime(),s.lastPlaybackProgressReportTicks=e.PositionTicks;var t=s.getUrl("Sessions/Playing/Progress");return s.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}(s.lastPlaybackProgressOptions)})).finally((function(){l()})),this.reportPlaybackProgressTimeout=r,this.reportPlaybackProgressPromise=a,this.reportPlaybackProgressCancel=function(){u=!0,l()},a}},{key:"reportOfflineActions",value:function(e){if(!e)throw new Error("null actions");var t=this.getUrl("Sync/OfflineActions");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"syncData",value:function(e){if(!e)throw new Error("null data");var t=this.getUrl("Sync/Data");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t,dataType:"json"})}},{key:"getReadySyncItems",value:function(e){if(!e)throw new Error("null deviceId");var t=this.getUrl("Sync/Items/Ready",{TargetId:e});return this.getJSON(t)}},{key:"reportSyncJobItemTransferred",value:function(e){if(!e)throw new Error("null syncJobItemId");var t=this.getUrl("Sync/JobItems/".concat(e,"/Transferred"));return this.ajax({type:"POST",url:t})}},{key:"cancelSyncItems",value:function(e,t){if(!e)throw new Error("null itemIds");var r=this.getUrl("Sync/".concat(t||this.deviceId(),"/Items"),{ItemIds:e.join(",")});return this.ajax({type:"DELETE",url:r})}},{key:"reportPlaybackStopped",value:function(e){if(!e)throw new Error("null options");this.lastPlaybackProgressReport=0,this.lastPlaybackProgressReportTicks=null,v(this),w(this);var t=this.getUrl("Sessions/Playing/Stopped");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"sendPlayCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null options");var r=this.getUrl("Sessions/".concat(e,"/Playing"),t);return this.ajax({type:"POST",url:r})}},{key:"sendCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null command");var r={type:"POST",url:this.getUrl("Sessions/".concat(e,"/Command"))};return r.data=JSON.stringify(t),r.contentType="application/json",this.ajax(r)}},{key:"sendMessageCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null options");var r={type:"POST",url:this.getUrl("Sessions/".concat(e,"/Message"))};return r.data=JSON.stringify(t),r.contentType="application/json",this.ajax(r)}},{key:"sendPlayStateCommand",value:function(e,t,r){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null command");var n=this.getUrl("Sessions/".concat(e,"/Playing/").concat(t),r||{});return this.ajax({type:"POST",url:n})}},{key:"getSyncPlayGroups",value:function(){var e=this.getUrl("SyncPlay/List");return this.ajax({type:"GET",url:e})}},{key:"createSyncPlayGroup",value:function(){var e=this.getUrl("SyncPlay/New");return this.ajax({type:"POST",url:e})}},{key:"joinSyncPlayGroup",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Join",e);return this.ajax({type:"POST",url:t})}},{key:"leaveSyncPlayGroup",value:function(){var e=this.getUrl("SyncPlay/Leave");return this.ajax({type:"POST",url:e})}},{key:"sendSyncPlayPing",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Ping",e);return this.ajax({type:"POST",url:t})}},{key:"requestSyncPlayStart",value:function(){var e=this.getUrl("SyncPlay/Play");return this.ajax({type:"POST",url:e})}},{key:"requestSyncPlayPause",value:function(){var e=this.getUrl("SyncPlay/Pause");return this.ajax({type:"POST",url:e})}},{key:"requestSyncPlaySeek",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Seek",e);return this.ajax({type:"POST",url:t})}},{key:"createPackageReview",value:function(e){var t=this.getUrl("Packages/Reviews/".concat(e.id),e);return this.ajax({type:"POST",url:t})}},{key:"getPackageReviews",value:function(e,t,r,n){if(!e)throw new Error("null packageId");var i={};t&&(i.MinRating=t),r&&(i.MaxRating=r),n&&(i.Limit=n);var o=this.getUrl("Packages/".concat(e,"/Reviews"),i);return this.getJSON(o)}},{key:"getSavedEndpointInfo",value:function(){return this._endPointInfo}},{key:"getEndpointInfo",value:function(){var e=this._endPointInfo;if(e)return Promise.resolve(e);var t=this;return this.getJSON(this.getUrl("System/Endpoint")).then((function(e){return k(t,e),e}))}},{key:"getLatestItems",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.getJSON(this.getUrl("Users/".concat(this.getCurrentUserId(),"/Items/Latest"),e))}},{key:"getFilters",value:function(e){return this.getJSON(this.getUrl("Items/Filters2",e))}},{key:"setSystemInfo",value:function(e){this._serverVersion=e.Version}},{key:"serverVersion",value:function(){return this._serverVersion}},{key:"isMinServerVersion",value:function(e){var t=this.serverVersion();return!!t&&function(e,t){e=e.split("."),t=t.split(".");for(var r=0,n=Math.max(e.length,t.length);ro)return 1}return 0}(t,e)>=0}},{key:"handleMessageReceived",value:function(e){b(this,e)}}])&&d(t.prototype,r),n&&d(t,n),e}();function J(e){return(J="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function R(e,t){for(var r=0;rt.length&&0===e.indexOf(t))}function H(e,t){return q(e,t)?e.substr(t.length):e}function z(e){return e?V(e)?e:"local:".concat(e):null}function K(e){e.Id=z(e.Id),e.SeriesId=z(e.SeriesId),e.SeasonId=z(e.SeasonId),e.AlbumId=z(e.AlbumId),e.ParentId=z(e.ParentId),e.ParentThumbItemId=z(e.ParentThumbItemId),e.ParentPrimaryImageItemId=z(e.ParentPrimaryImageItemId),e.PrimaryImageItemId=z(e.PrimaryImageItemId),e.ParentLogoItemId=z(e.ParentLogoItemId),e.ParentBackdropItemId=z(e.ParentBackdropItemId),e.ParentBackdropImageTags=null}function Q(e,t,r){return e.getLocalFolders(t,r).then((function(r){var n=null;return r.length>0&&(n={Name:e.downloadsTitleText||"Downloads",ServerId:t,Id:"localview",Type:"localview",IsFolder:!0}),Promise.resolve(n)}))}var X=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&D(e,t)}(o,e);var t,r,n,i=M(o);function o(e,t,r,n,a,s,u){var l;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,o),(l=i.call(this,e,t,r,n,a,s)).localAssetManager=u,l}return t=o,(r=[{key:"getPlaybackInfo",value:function(e,t,r){var n=function(){return C.prototype.getPlaybackInfo.call(i,e,t,r)};if(V(e))return this.localAssetManager.getLocalItem(this.serverId(),W(e)).then((function(e){return{MediaSources:e.Item.MediaSources.map((function(e){return e.SupportsDirectPlay=!0,e.SupportsDirectStream=!1,e.SupportsTranscoding=!1,e.IsLocal=!0,e}))}}),n);var i=this;return this.localAssetManager.getLocalItem(this.serverId(),e).then((function(o){if(o){var a=o.Item.MediaSources.map((function(e){return e.SupportsDirectPlay=!0,e.SupportsDirectStream=!1,e.SupportsTranscoding=!1,e.IsLocal=!0,e}));return i.localAssetManager.fileExists(o.LocalPath).then((function(n){if(n){var o={MediaSources:a};return Promise.resolve(o)}return C.prototype.getPlaybackInfo.call(i,e,t,r)}),n)}return C.prototype.getPlaybackInfo.call(i,e,t,r)}),n)}},{key:"getItems",value:function(e,t){var r,n=this.serverInfo();if(n&&"localview"===t.ParentId)return this.getLocalFolders(n.Id,e).then((function(e){var t={Items:e,TotalRecordCount:e.length};return Promise.resolve(t)}));if(n&&t&&(V(t.ParentId)||V(t.SeriesId)||V(t.SeasonId)||G(t.ParentId)||V(t.AlbumIds)))return this.localAssetManager.getViewItems(n.Id,e,t).then((function(e){e.forEach((function(e){K(e)}));var t={Items:e,TotalRecordCount:e.length};return Promise.resolve(t)}));if(t&&t.ExcludeItemIds&&t.ExcludeItemIds.length){var i=t.ExcludeItemIds.split(",");for(r=0;r0?Promise.resolve(r[0]):Promise.reject()})):V(t)&&(r=this.serverInfo())?this.localAssetManager.getLocalItem(r.Id,W(t)).then((function(e){return K(e.Item),Promise.resolve(e.Item)})):C.prototype.getItem.call(this,e,t)}},{key:"getLocalFolders",value:function(e){var t=this.serverInfo();return e=e||t.UserId,this.localAssetManager.getViews(t.Id,e)}},{key:"getNextUpEpisodes",value:function(e){return e.SeriesId&&V(e.SeriesId)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getNextUpEpisodes.call(this,e)}},{key:"getSeasons",value:function(e,t){return V(e)?(t.SeriesId=e,t.IncludeItemTypes="Season",this.getItems(this.getCurrentUserId(),t)):C.prototype.getSeasons.call(this,e,t)}},{key:"getEpisodes",value:function(e,t){return V(t.SeasonId)||V(t.seasonId)||V(e)?(t.SeriesId=e,t.IncludeItemTypes="Episode",this.getItems(this.getCurrentUserId(),t)):C.prototype.getEpisodes.call(this,e,t)}},{key:"getLatestOfflineItems",value:function(e){e.SortBy="DateCreated",e.SortOrder="Descending";var t=this.serverInfo();return t?this.localAssetManager.getViewItems(t.Id,null,e).then((function(e){return e.forEach((function(e){K(e)})),Promise.resolve(e)})):Promise.resolve([])}},{key:"getThemeMedia",value:function(e,t,r){return G(t)||V(t)||B(t)?Promise.reject():C.prototype.getThemeMedia.call(this,e,t,r)}},{key:"getSpecialFeatures",value:function(e,t){return V(t)?Promise.resolve([]):C.prototype.getSpecialFeatures.call(this,e,t)}},{key:"getSimilarItems",value:function(e,t){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getSimilarItems.call(this,e,t)}},{key:"updateFavoriteStatus",value:function(e,t,r){return V(t)?Promise.resolve():C.prototype.updateFavoriteStatus.call(this,e,t,r)}},{key:"getScaledImageUrl",value:function(e,t){if(V(e)||t&&t.itemid&&V(t.itemid)){var r=this.serverInfo(),n=W(e);return this.localAssetManager.getImageUrl(r.Id,n,t)}return C.prototype.getScaledImageUrl.call(this,e,t)}},{key:"reportPlaybackStart",value:function(e){if(!e)throw new Error("null options");return V(e.ItemId)?Promise.resolve():C.prototype.reportPlaybackStart.call(this,e)}},{key:"reportPlaybackProgress",value:function(e){if(!e)throw new Error("null options");if(V(e.ItemId)){var t=this.serverInfo();if(t){var r=this;return this.localAssetManager.getLocalItem(t.Id,W(e.ItemId)).then((function(t){var n=t.Item;return"Video"===n.MediaType||"AudioBook"===n.Type?(n.UserData=n.UserData||{},n.UserData.PlaybackPositionTicks=e.PositionTicks,n.UserData.PlayedPercentage=Math.min(n.RunTimeTicks?(e.PositionTicks||0)/n.RunTimeTicks*100:0,100),r.localAssetManager.addOrUpdateLocalItem(t)):Promise.resolve()}))}return Promise.resolve()}return C.prototype.reportPlaybackProgress.call(this,e)}},{key:"reportPlaybackStopped",value:function(e){if(!e)throw new Error("null options");if(V(e.ItemId)){var t=this.serverInfo(),r={Date:(new Date).getTime(),ItemId:W(e.ItemId),PositionTicks:e.PositionTicks,ServerId:t.Id,Type:0,UserId:this.getCurrentUserId()};return this.localAssetManager.recordUserAction(r)}return C.prototype.reportPlaybackStopped.call(this,e)}},{key:"getIntros",value:function(e){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getIntros.call(this,e)}},{key:"getInstantMixFromItem",value:function(e,t){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getInstantMixFromItem.call(this,e,t)}},{key:"getItemDownloadUrl",value:function(e){if(V(e)){var t=this.serverInfo();if(t)return this.localAssetManager.getLocalItem(t.Id,W(e)).then((function(e){return Promise.resolve(e.LocalPath)}))}return C.prototype.getItemDownloadUrl.call(this,e)}}])&&R(t.prototype,r),n&&R(t,n),o}(C);function Y(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};!1!==t.reportCapabilities&&e.reportCapabilities(s),e.enableAutomaticBitrateDetection=t.enableAutomaticBitrateDetection,!1!==t.enableWebSocket&&(console.log("calling apiClient.ensureWebSocket"),e.ensureWebSocket())}function d(e,t,r){return u._getOrAddApiClient(e,t),(u.onLocalUserSignedIn?u.onLocalUserSignedIn.call(u,r):Promise.resolve()).then((function(){i.trigger(u,"localusersignedin",[r])}))}function h(e,t){return ae({type:"GET",url:ie(t,"System/Info"),dataType:"json",headers:{"X-MediaBrowser-Token":e.AccessToken}}).then((function(t){return ne(e,t),Promise.resolve()}),(function(){return e.UserId=null,e.AccessToken=null,Promise.resolve()}))}function v(e){var t={serverId:(e.serverInfo()||{}).Id};return e.logout().then((function(){i.trigger(u,"localusersignedout",[t])}),(function(){i.trigger(u,"localusersignedout",[t])}))}function f(e){if(e.Address&&e.EndpointAddress){var t=e.EndpointAddress.split(":")[0],r=e.Address.split(":");if(r.length>1){var n=r[r.length-1];isNaN(parseInt(n))||(t+=":".concat(n))}return ue(t)}return null}function g(e){var t=[],r=[];return!e.manualAddressOnly&&e.LocalAddress&&-1===r.indexOf(e.LocalAddress)&&(t.push({url:e.LocalAddress,mode:Z,timeout:0}),r.push(t[t.length-1].url)),e.ManualAddress&&-1===r.indexOf(e.ManualAddress)&&(t.push({url:e.ManualAddress,mode:ee,timeout:100}),r.push(t[t.length-1].url)),!e.manualAddressOnly&&e.RemoteAddress&&-1===r.indexOf(e.RemoteAddress)&&(t.push({url:e.RemoteAddress,mode:$,timeout:200}),r.push(t[t.length-1].url)),console.log("tryReconnect: "+r.join("|")),new Promise((function(e,r){var n={};n.numAddresses=t.length,n.rejects=0,t.map((function(t){setTimeout((function(){n.resolved||function(e,t,r,n,i){console.log("getTryConnectPromise "+e),ae({url:ie(e,"system/info/public"),timeout:2e4,type:"GET",dataType:"json"}).then((function(i){r.resolved||(r.resolved=!0,console.log("Reconnect succeeded to "+e),n({url:e,connectionMode:t,data:i}))}),(function(){console.log("Reconnect failed to "+e),r.resolved||(r.rejects++,r.rejects>=r.numAddresses&&i())}))}(t.url,t.mode,n,e,r)}),t.timeout)}))}))}function p(e,t){var r={ManualAddress:e,LastConnectionMode:ee};return u.connectToServer(r,t).then((function(e){return"Unavailable"===e.State?Promise.reject():e}))}this._apiClients=[],u._minServerVersion="3.2.33",u.appVersion=function(){return n},u.appName=function(){return r},u.capabilities=function(){return s},u.deviceId=function(){return a},u.credentialProvider=function(){return t},u.getServerInfo=function(e){return t.credentials().Servers.filter((function(t){return t.Id===e}))[0]},u.getLastUsedServer=function(){var e=t.credentials().Servers;return e.sort((function(e,t){return(t.DateLastAccessed||0)-(e.DateLastAccessed||0)})),e.length?e[0]:null},u.addApiClient=function(e){u._apiClients.push(e);var r=t.credentials().Servers.filter((function(t){return le(t.ManualAddress,e.serverAddress())||le(t.LocalAddress,e.serverAddress())||le(t.RemoteAddress,e.serverAddress())})),n=r.length?r[0]:e.serverInfo();if(n.DateLastAccessed=(new Date).getTime(),n.LastConnectionMode=ee,n.ManualAddress=e.serverAddress(),e.manualAddressOnly&&(n.manualAddressOnly=!0),e.serverInfo(n),e.onAuthenticated=function(e,t){return l(e,t,{},!0)},!r.length){var o=t.credentials();o.Servers=[n],t.credentials(o)}i.trigger(u,"apiclientcreated",[e])},u.clearData=function(){console.log("connection manager clearing data");var e=t.credentials();e.Servers=[],t.credentials(e)},u._getOrAddApiClient=function(e,t){var s=u.getApiClient(e.Id);return s||(s=new C(t,r,n,o,a),u._apiClients.push(s),s.serverInfo(e),s.onAuthenticated=function(e,t){return l(e,t,{},!0)},i.trigger(u,"apiclientcreated",[s])),console.log("returning instance from getOrAddApiClient"),s},u.getOrCreateApiClient=function(e){var r=t.credentials().Servers.filter((function(t){return le(t.Id,e)}));if(!r.length)throw new Error("Server not found: ".concat(e));var n=r[0];return u._getOrAddApiClient(n,te(n,n.LastConnectionMode))},u.user=function(e){return new Promise((function(t,r){var n;e&&e.getCurrentUserId()&&e&&e.getCurrentUserId()&&e.getCurrentUser().then((function(e){var r=function(e){return e&&e.PrimaryImageTag?{url:u.getApiClient(e).getUserImageUrl(e.Id,{tag:e.PrimaryImageTag,type:"Primary"}),supportsParams:!0}:{url:null,supportsParams:!1}}(n=e);t({localUser:n,name:n?n.Name:null,imageUrl:r.url,supportsImageParams:r.supportsParams})}))}))},u.logout=function(){for(var e=[],r=0,n=u._apiClients.length;ro)return 1}return 0}(u.minServerVersion(),o.Version)?(console.log("minServerVersion requirement not met. Server version: "+o.Version),n({State:"ServerUpdateNeeded",Servers:[e]})):e.Id&&o.Id!==e.Id?(console.log("http request succeeded, but found a different server Id than what was expected"),re(0,n)):function e(r,n,o,a,s,l){var v=arguments.length>6&&void 0!==arguments[6]?arguments[6]:{},f=t.credentials();if(!1===v.enableAutoLogin)r.UserId=null,r.AccessToken=null;else if(r.AccessToken&&s)return void h(r,a).then((function(){e(r,n,o,a,!1,l,v)}));ne(r,n),r.LastConnectionMode=o,!1!==v.updateDateLastAccessed&&(r.DateLastAccessed=(new Date).getTime());t.addOrUpdateServer(f.Servers,r),t.credentials(f);var g={Servers:[]};g.ApiClient=u._getOrAddApiClient(r,a),g.ApiClient.setSystemInfo(n),g.State=r.AccessToken&&!1!==v.enableAutoLogin?"SignedIn":"ServerSignIn",g.Servers.push(r),g.ApiClient.enableAutomaticBitrateDetection=v.enableAutomaticBitrateDetection,g.ApiClient.updateServerInfo(r,a);var p=function(){l(g),i.trigger(u,"connected",[g])};"SignedIn"===g.State?(c(g.ApiClient,v),g.ApiClient.getCurrentUser().then((function(e){d(r,a,e).then(p,p)}),p)):p()}(e,o,s,a,!0,n,r)}),(function(){re(0,n)}))}))},u.connectToAddress=function(e,t){if(!e)return Promise.reject();e=ue(e);var r=[];/^[^:]+:\/\//.test(e)?r.push(e):(r.push("https://".concat(e)),r.push("http://".concat(e)));var n=0;return p(r[n],t).catch((function e(){return console.log("connectToAddress ".concat(r[n]," failed")),++n=r.length)return j(t,i);var o=r[n];return t.getDownloadSpeed(o.bytes).then((function(i){return i=20?Promise.reject():function(e){var t=[],r=[],n=e.serverInfo();return n.LocalAddress&&-1===r.indexOf(n.LocalAddress)&&(t.push({url:n.LocalAddress,timeout:0}),r.push(t[t.length-1].url)),n.ManualAddress&&-1===r.indexOf(n.ManualAddress)&&(t.push({url:n.ManualAddress,timeout:100}),r.push(t[t.length-1].url)),n.RemoteAddress&&-1===r.indexOf(n.RemoteAddress)&&(t.push({url:n.RemoteAddress,timeout:200}),r.push(t[t.length-1].url)),console.log("tryReconnect: "+r.join("|")),new Promise((function(r,n){var i={};i.numAddresses=t.length,i.rejects=0,t.map((function(t){setTimeout((function(){i.resolved||function(e,t,r,n,i){console.log("getTryConnectPromise "+t),I(e.getUrl("system/info/public",null,t),{method:"GET",accept:"application/json"},15e3).then((function(){r.resolved||(r.resolved=!0,console.log("Reconnect succeeded to "+t),e.serverAddress(t),n())}),(function(){r.resolved||(console.log("Reconnect failed to "+t),r.rejects++,r.rejects>=r.numAddresses&&i())}))}(e,t.url,i,r,n)}),t.timeout)}))}))}(t).catch((function(n){return console.log("error in tryReconnectInternal: "+(n||"")),new Promise((function(n,i){setTimeout((function(){e(t,r+1).then(n,i)}),500)}))}))}(r).then((function(){return console.log("Reconnect succeesed"),e.url=e.url.replace(i,r.serverAddress()),r.fetchWithFailover(e,!1)})).catch((function(t){throw console.log("Reconnect failed"),y(r,e.url,{}),t}))}))}},{key:"fetch",value:function(e,t){if(!e)return Promise.reject("Request cannot be null");if(e.headers=e.headers||{},!1!==t&&this.setRequestHeaders(e.headers),!1===this.enableAutomaticNetworking||"GET"!==e.type){console.log("Requesting url without automatic networking: ".concat(e.url));var r=this;return S(e).then((function(t){return r.lastFetch=(new Date).getTime(),t.status<400?"json"===e.dataType||"application/json"===e.headers.accept?t.json():"text"===e.dataType||0===(t.headers.get("Content-Type")||"").toLowerCase().indexOf("text/")?t.text():t:(y(r,e.url,t),Promise.reject(t))})).catch((function(t){return y(r,e.url,{}),Promise.reject(t)}))}return this.fetchWithFailover(e,!0)}},{key:"setAuthenticationInfo",value:function(e,t){this._currentUser=null,this._serverInfo.AccessToken=e,this._serverInfo.UserId=t,v(this)}},{key:"serverInfo",value:function(e){return e&&(this._serverInfo=e),this._serverInfo}},{key:"getCurrentUserId",value:function(){return this._serverInfo.UserId}},{key:"accessToken",value:function(){return this._serverInfo.AccessToken}},{key:"serverId",value:function(){return this.serverInfo().Id}},{key:"serverName",value:function(){return this.serverInfo().Name}},{key:"ajax",value:function(e,t){return e?this.fetch(e,t):Promise.reject("Request cannot be null")}},{key:"getCurrentUser",value:function(e){if(this._currentUser)return Promise.resolve(this._currentUser);var t=this.getCurrentUserId();if(!t)return Promise.reject();var r,n=this,i=this.getUser(t).then((function(e){return l.setItem("user-".concat(e.Id,"-").concat(e.ServerId),JSON.stringify(e)),n._currentUser=e,e})).catch((function(e){if(!e.status&&t&&n.accessToken()&&(r=T(n,t)))return Promise.resolve(r);throw e}));return!this.lastFetch&&!1!==e&&(r=T(n,t))?Promise.resolve(r):i}},{key:"isLoggedIn",value:function(){var e=this.serverInfo();return!!(e&&e.UserId&&e.AccessToken)}},{key:"logout",value:function(){var e=this;g(this),this.closeWebSocket();var t=function(){var t=e.serverInfo();t&&t.UserId&&t.Id&&l.removeItem("user-".concat(t.UserId,"-").concat(t.Id)),e.setAuthenticationInfo(null,null)};if(this.accessToken()){var r=this.getUrl("Sessions/Logout");return this.ajax({type:"POST",url:r}).then(t,t)}return t(),Promise.resolve()}},{key:"authenticateUserByName",value:function(e,t){var r=this;if(!e)return Promise.reject();var n=this.getUrl("Users/authenticatebyname");return new Promise((function(i,o){var a={Username:e,Pw:t||""};r.ajax({type:"POST",url:n,data:JSON.stringify(a),dataType:"json",contentType:"application/json"}).then((function(e){var t=function(){v(r),i(e)};r.onAuthenticated?r.onAuthenticated(r,e).then(t):t()})).catch(o)}))}},{key:"ensureWebSocket",value:function(){if(!this.isWebSocketOpenOrConnecting()&&this.isWebSocketSupported())try{this.openWebSocket()}catch(e){console.log("Error opening web socket: ".concat(e))}}},{key:"openWebSocket",value:function(){var e=this.accessToken();if(!e)throw new Error("Cannot open web socket without access token.");var t=this.getUrl("socket");t=p(t,"emby/socket","embywebsocket"),t=p(t,"https:","wss:"),t=p(t,"http:","ws:"),t+="?api_key=".concat(e),t+="&deviceId=".concat(this.deviceId()),console.log("opening web socket with url: ".concat(t));var r,n,o=new WebSocket(t);o.onmessage=P.bind(this),o.onopen=E.bind(this),o.onerror=A.bind(this),r=this,(n=o).onclose=function(){console.log("web socket closed"),O(n),r._webSocket===n&&(console.log("nulling out web socket"),r._webSocket=null),setTimeout((function(){i.trigger(r,"websocketclose")}),0)},this._webSocket=o}},{key:"closeWebSocket",value:function(){var e=this._webSocket;e&&e.readyState===WebSocket.OPEN&&e.close()}},{key:"sendWebSocketMessage",value:function(e,t){console.log("Sending web socket message: ".concat(e));var r={MessageType:e};t&&(r.Data=t),r=JSON.stringify(r),this._webSocket.send(r)}},{key:"sendMessage",value:function(e,t){this.isWebSocketOpen()&&this.sendWebSocketMessage(e,t)}},{key:"isMessageChannelOpen",value:function(){return this.isWebSocketOpen()}},{key:"isWebSocketOpen",value:function(){var e=this._webSocket;return!!e&&e.readyState===WebSocket.OPEN}},{key:"isWebSocketOpenOrConnecting",value:function(){var e=this._webSocket;return!!e&&(e.readyState===WebSocket.OPEN||e.readyState===WebSocket.CONNECTING)}},{key:"get",value:function(e){return this.ajax({type:"GET",url:e})}},{key:"getJSON",value:function(e,t){return this.fetch({url:e,type:"GET",dataType:"json",headers:{accept:"application/json"}},t)}},{key:"updateServerInfo",value:function(e,t){if(null==e)throw new Error("server cannot be null");if(this.serverInfo(e),!t)throw new Error("serverUrl cannot be null. serverInfo: ".concat(JSON.stringify(e)));console.log("Setting server address to ".concat(t)),this.serverAddress(t)}},{key:"isWebSocketSupported",value:function(){try{return null!=WebSocket}catch(e){return!1}}},{key:"clearAuthenticationInfo",value:function(){this.setAuthenticationInfo(null,null)}},{key:"encodeName",value:function(e){var t=m({name:e=(e=(e=e.split("/").join("-")).split("&").join("-")).split("?").join("-")});return t.substring(t.indexOf("=")+1).replace("'","%27")}},{key:"getServerTime",value:function(){var e=this.getUrl("GetUTCTime");return this.ajax({type:"GET",url:e})}},{key:"getDownloadSpeed",value:function(e){var t=this.getUrl("Playback/BitrateTest",{Size:e}),r=(new Date).getTime();return this.ajax({type:"GET",url:t,timeout:5e3}).then((function(){var t=((new Date).getTime()-r)/1e3,n=e/t;return Math.round(8*n)}))}},{key:"detectBitrate",value:function(e){if(!e&&this.lastDetectedBitrate&&(new Date).getTime()-(this.lastDetectedBitrateTime||0)<=36e5)return Promise.resolve(this.lastDetectedBitrate);var t=this;return this.getEndpointInfo().then((function(e){return N(t,e)}),(function(e){return N(t,{})}))}},{key:"getItem",value:function(e,t){if(!t)throw new Error("null itemId");var r=e?this.getUrl("Users/".concat(e,"/Items/").concat(t)):this.getUrl("Items/".concat(t));return this.getJSON(r)}},{key:"getRootFolder",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/Items/Root"));return this.getJSON(t)}},{key:"getNotificationSummary",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Notifications/".concat(e,"/Summary"));return this.getJSON(t)}},{key:"getNotifications",value:function(e,t){if(!e)throw new Error("null userId");var r=this.getUrl("Notifications/".concat(e),t||{});return this.getJSON(r)}},{key:"markNotificationsRead",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null idList");var n=r?"Read":"Unread",i={UserId:e,Ids:t.join(",")},o=this.getUrl("Notifications/".concat(e,"/").concat(n),i);return this.ajax({type:"POST",url:o})}},{key:"getRemoteImageProviders",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages/Providers"),e);return this.getJSON(r)}},{key:"getAvailableRemoteImages",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages"),e);return this.getJSON(r)}},{key:"downloadRemoteImage",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages/Download"),e);return this.ajax({type:"POST",url:r})}},{key:"getRecordingFolders",value:function(e){var t=this.getUrl("LiveTv/Recordings/Folders",{userId:e});return this.getJSON(t)}},{key:"getLiveTvInfo",value:function(e){var t=this.getUrl("LiveTv/Info",e||{});return this.getJSON(t)}},{key:"getLiveTvGuideInfo",value:function(e){var t=this.getUrl("LiveTv/GuideInfo",e||{});return this.getJSON(t)}},{key:"getLiveTvChannel",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Channels/".concat(e),r);return this.getJSON(n)}},{key:"getLiveTvChannels",value:function(e){var t=this.getUrl("LiveTv/Channels",e||{});return this.getJSON(t)}},{key:"getLiveTvPrograms",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return e.channelIds&&e.channelIds.length>1800?this.ajax({type:"POST",url:this.getUrl("LiveTv/Programs"),data:JSON.stringify(e),contentType:"application/json",dataType:"json"}):this.ajax({type:"GET",url:this.getUrl("LiveTv/Programs",e),dataType:"json"})}},{key:"getLiveTvRecommendedPrograms",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.ajax({type:"GET",url:this.getUrl("LiveTv/Programs/Recommended",e),dataType:"json"})}},{key:"getLiveTvRecordings",value:function(e){var t=this.getUrl("LiveTv/Recordings",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingSeries",value:function(e){var t=this.getUrl("LiveTv/Recordings/Series",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingGroups",value:function(e){var t=this.getUrl("LiveTv/Recordings/Groups",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingGroup",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Recordings/Groups/".concat(e));return this.getJSON(t)}},{key:"getLiveTvRecording",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Recordings/".concat(e),r);return this.getJSON(n)}},{key:"getLiveTvProgram",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Programs/".concat(e),r);return this.getJSON(n)}},{key:"deleteLiveTvRecording",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Recordings/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"cancelLiveTvTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Timers/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"getLiveTvTimers",value:function(e){var t=this.getUrl("LiveTv/Timers",e||{});return this.getJSON(t)}},{key:"getLiveTvTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Timers/".concat(e));return this.getJSON(t)}},{key:"getNewLiveTvTimerDefaults",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("LiveTv/Timers/Defaults",e);return this.getJSON(t)}},{key:"createLiveTvTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/Timers");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateLiveTvTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/Timers/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"resetLiveTvTuner",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Tuners/".concat(e,"/Reset"));return this.ajax({type:"POST",url:t})}},{key:"getLiveTvSeriesTimers",value:function(e){var t=this.getUrl("LiveTv/SeriesTimers",e||{});return this.getJSON(t)}},{key:"getLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e));return this.getJSON(t)}},{key:"cancelLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"createLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/SeriesTimers");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"getRegistrationInfo",value:function(e){var t=this.getUrl("Registrations/".concat(e));return this.getJSON(t)}},{key:"getSystemInfo",value:function(e){var t=this.getUrl("System/Info"),r=this;return this.getJSON(t).then((function(e){return r.setSystemInfo(e),Promise.resolve(e)}))}},{key:"getSyncStatus",value:function(){var e=this.getUrl("Sync/"+itemId+"/Status");return this.ajax({url:e,type:"POST",dataType:"json",contentType:"application/json",data:JSON.stringify({TargetId:this.deviceId()})})}},{key:"getPublicSystemInfo",value:function(){var e=this.getUrl("System/Info/Public"),t=this;return this.getJSON(e).then((function(e){return t.setSystemInfo(e),Promise.resolve(e)}))}},{key:"getInstantMixFromItem",value:function(e,t){var r=this.getUrl("Items/".concat(e,"/InstantMix"),t);return this.getJSON(r)}},{key:"getEpisodes",value:function(e,t){var r=this.getUrl("Shows/".concat(e,"/Episodes"),t);return this.getJSON(r)}},{key:"getDisplayPreferences",value:function(e,t,r){var n=this.getUrl("DisplayPreferences/".concat(e),{userId:t,client:r});return this.getJSON(n)}},{key:"updateDisplayPreferences",value:function(e,t,r,n){var i=this.getUrl("DisplayPreferences/".concat(e),{userId:r,client:n});return this.ajax({type:"POST",url:i,data:JSON.stringify(t),contentType:"application/json"})}},{key:"getSeasons",value:function(e,t){var r=this.getUrl("Shows/".concat(e,"/Seasons"),t);return this.getJSON(r)}},{key:"getSimilarItems",value:function(e,t){var r=this.getUrl("Items/".concat(e,"/Similar"),t);return this.getJSON(r)}},{key:"getCultures",value:function(){var e=this.getUrl("Localization/cultures");return this.getJSON(e)}},{key:"getCountries",value:function(){var e=this.getUrl("Localization/countries");return this.getJSON(e)}},{key:"getPlaybackInfo",value:function(e,t,r){var n={DeviceProfile:r};return this.ajax({url:this.getUrl("Items/".concat(e,"/PlaybackInfo"),t),type:"POST",data:JSON.stringify(n),contentType:"application/json",dataType:"json"})}},{key:"getLiveStreamMediaInfo",value:function(e){var t={LiveStreamId:e};return this.ajax({url:this.getUrl("LiveStreams/MediaInfo"),type:"POST",data:JSON.stringify(t),contentType:"application/json",dataType:"json"})}},{key:"getIntros",value:function(e){return this.getJSON(this.getUrl("Users/".concat(this.getCurrentUserId(),"/Items/").concat(e,"/Intros")))}},{key:"getDirectoryContents",value:function(e,t){if(!e)throw new Error("null path");if("string"!=typeof e)throw new Error("invalid path");(t=t||{}).path=e;var r=this.getUrl("Environment/DirectoryContents",t);return this.getJSON(r)}},{key:"getNetworkShares",value:function(e){if(!e)throw new Error("null path");var t={};t.path=e;var r=this.getUrl("Environment/NetworkShares",t);return this.getJSON(r)}},{key:"getParentPath",value:function(e){if(!e)throw new Error("null path");var t={};t.path=e;var r=this.getUrl("Environment/ParentPath",t);return this.ajax({type:"GET",url:r,dataType:"text"})}},{key:"getDrives",value:function(){var e=this.getUrl("Environment/Drives");return this.getJSON(e)}},{key:"getNetworkDevices",value:function(){var e=this.getUrl("Environment/NetworkDevices");return this.getJSON(e)}},{key:"cancelPackageInstallation",value:function(e){if(!e)throw new Error("null installationId");var t=this.getUrl("Packages/Installing/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"refreshItem",value:function(e,t){if(!e)throw new Error("null itemId");var r=this.getUrl("Items/".concat(e,"/Refresh"),t||{});return this.ajax({type:"POST",url:r})}},{key:"installPlugin",value:function(e,t,r){if(!e)throw new Error("null name");var n={AssemblyGuid:t};r&&(n.version=r);var i=this.getUrl("Packages/Installed/".concat(e),n);return this.ajax({type:"POST",url:i})}},{key:"restartServer",value:function(){var e=this.getUrl("System/Restart");return this.ajax({type:"POST",url:e})}},{key:"shutdownServer",value:function(){var e=this.getUrl("System/Shutdown");return this.ajax({type:"POST",url:e})}},{key:"getPackageInfo",value:function(e,t){if(!e)throw new Error("null name");var r={AssemblyGuid:t},n=this.getUrl("Packages/".concat(e),r);return this.getJSON(n)}},{key:"getVirtualFolders",value:function(){var e="Library/VirtualFolders";return e=this.getUrl(e),this.getJSON(e)}},{key:"getPhysicalPaths",value:function(){var e=this.getUrl("Library/PhysicalPaths");return this.getJSON(e)}},{key:"getServerConfiguration",value:function(){var e=this.getUrl("System/Configuration");return this.getJSON(e)}},{key:"getDevicesOptions",value:function(){var e=this.getUrl("System/Configuration/devices");return this.getJSON(e)}},{key:"getContentUploadHistory",value:function(){var e=this.getUrl("Devices/CameraUploads",{DeviceId:this.deviceId()});return this.getJSON(e)}},{key:"getNamedConfiguration",value:function(e){var t=this.getUrl("System/Configuration/".concat(e));return this.getJSON(t)}},{key:"getScheduledTasks",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("ScheduledTasks",e);return this.getJSON(t)}},{key:"startScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/Running/".concat(e));return this.ajax({type:"POST",url:t})}},{key:"getScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/".concat(e));return this.getJSON(t)}},{key:"getNextUpEpisodes",value:function(e){var t=this.getUrl("Shows/NextUp",e);return this.getJSON(t)}},{key:"stopScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/Running/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"getPluginConfiguration",value:function(e){if(!e)throw new Error("null Id");var t=this.getUrl("Plugins/".concat(e,"/Configuration"));return this.getJSON(t)}},{key:"getAvailablePlugins",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};e.PackageType="UserInstalled";var t=this.getUrl("Packages",e);return this.getJSON(t)}},{key:"uninstallPlugin",value:function(e){if(!e)throw new Error("null Id");var t=this.getUrl("Plugins/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"removeVirtualFolder",value:function(e,t){if(!e)throw new Error("null name");var r="Library/VirtualFolders";return r=this.getUrl(r,{refreshLibrary:!!t,name:e}),this.ajax({type:"DELETE",url:r})}},{key:"addVirtualFolder",value:function(e,t,r,n){if(!e)throw new Error("null name");var i={};t&&(i.collectionType=t),i.refreshLibrary=!!r,i.name=e;var o="Library/VirtualFolders";return o=this.getUrl(o,i),this.ajax({type:"POST",url:o,data:JSON.stringify({LibraryOptions:n}),contentType:"application/json"})}},{key:"updateVirtualFolderOptions",value:function(e,t){if(!e)throw new Error("null name");var r="Library/VirtualFolders/LibraryOptions";return r=this.getUrl(r),this.ajax({type:"POST",url:r,data:JSON.stringify({Id:e,LibraryOptions:t}),contentType:"application/json"})}},{key:"renameVirtualFolder",value:function(e,t,r){if(!e)throw new Error("null name");var n="Library/VirtualFolders/Name";return n=this.getUrl(n,{refreshLibrary:!!r,newName:t,name:e}),this.ajax({type:"POST",url:n})}},{key:"addMediaPath",value:function(e,t,r,n){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null mediaPath");var i="Library/VirtualFolders/Paths",o={Path:t};return r&&(o.NetworkPath=r),i=this.getUrl(i,{refreshLibrary:!!n}),this.ajax({type:"POST",url:i,data:JSON.stringify({Name:e,PathInfo:o}),contentType:"application/json"})}},{key:"updateMediaPath",value:function(e,t){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null pathInfo");var r="Library/VirtualFolders/Paths/Update";return r=this.getUrl(r),this.ajax({type:"POST",url:r,data:JSON.stringify({Name:e,PathInfo:t}),contentType:"application/json"})}},{key:"removeMediaPath",value:function(e,t,r){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null mediaPath");var n="Library/VirtualFolders/Paths";return n=this.getUrl(n,{refreshLibrary:!!r,path:t,name:e}),this.ajax({type:"DELETE",url:n})}},{key:"deleteUser",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("Users/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"deleteUserImage",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null imageType");var n=this.getUrl("Users/".concat(e,"/Images/").concat(t));return null!=r&&(n+="/".concat(r)),this.ajax({type:"DELETE",url:n})}},{key:"deleteItemImage",value:function(e,t,r){if(!t)throw new Error("null imageType");var n=this.getUrl("Items/".concat(e,"/Images"));return n+="/".concat(t),null!=r&&(n+="/".concat(r)),this.ajax({type:"DELETE",url:n})}},{key:"deleteItem",value:function(e){if(!e)throw new Error("null itemId");var t=this.getUrl("Items/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"stopActiveEncodings",value:function(e){var t={deviceId:this.deviceId()};e&&(t.PlaySessionId=e);var r=this.getUrl("Videos/ActiveEncodings",t);return this.ajax({type:"DELETE",url:r})}},{key:"reportCapabilities",value:function(e){var t=this.getUrl("Sessions/Capabilities/Full");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateItemImageIndex",value:function(e,t,r,n){if(!t)throw new Error("null imageType");var i={newIndex:n},o=this.getUrl("Items/".concat(e,"/Images/").concat(t,"/").concat(r,"/Index"),i);return this.ajax({type:"POST",url:o})}},{key:"getItemImageInfos",value:function(e){var t=this.getUrl("Items/".concat(e,"/Images"));return this.getJSON(t)}},{key:"getCriticReviews",value:function(e,t){if(!e)throw new Error("null itemId");var r=this.getUrl("Items/".concat(e,"/CriticReviews"),t);return this.getJSON(r)}},{key:"getItemDownloadUrl",value:function(e){if(!e)throw new Error("itemId cannot be empty");var t="Items/".concat(e,"/Download");return this.getUrl(t,{api_key:this.accessToken()})}},{key:"getSessions",value:function(e){var t=this.getUrl("Sessions",e);return this.getJSON(t)}},{key:"uploadUserImage",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null imageType");if(!r)throw new Error("File must be an image.");if(!r.type.startsWith("image/"))throw new Error("File must be an image.");var n=this;return new Promise((function(i,o){var a=new FileReader;a.onerror=function(){o()},a.onabort=function(){o()},a.onload=function(a){var s=a.target.result.split(",")[1],u=n.getUrl("Users/".concat(e,"/Images/").concat(t));n.ajax({type:"POST",url:u,data:s,contentType:"image/".concat(r.name.substring(r.name.lastIndexOf(".")+1))}).then(i,o)},a.readAsDataURL(r)}))}},{key:"uploadItemImage",value:function(e,t,r){if(!e)throw new Error("null itemId");if(!t)throw new Error("null imageType");if(!r)throw new Error("File must be an image.");if(!r.type.startsWith("image/"))throw new Error("File must be an image.");var n=this.getUrl("Items/".concat(e,"/Images"));n+="/".concat(t);var i=this;return new Promise((function(e,t){var o=new FileReader;o.onerror=function(){t()},o.onabort=function(){t()},o.onload=function(o){var a=o.target.result.split(",")[1];i.ajax({type:"POST",url:n,data:a,contentType:"image/".concat(r.name.substring(r.name.lastIndexOf(".")+1))}).then(e,t)},o.readAsDataURL(r)}))}},{key:"getInstalledPlugins",value:function(){var e=this.getUrl("Plugins",{});return this.getJSON(e)}},{key:"getUser",value:function(e){if(!e)throw new Error("Must supply a userId");var t=this.getUrl("Users/".concat(e));return this.getJSON(t)}},{key:"getStudio",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Studios/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getGenre",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Genres/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getMusicGenre",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("MusicGenres/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getArtist",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Artists/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getPerson",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Persons/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getPublicUsers",value:function(){var e=this.getUrl("users/public");return this.ajax({type:"GET",url:e,dataType:"json"},!1)}},{key:"getUsers",value:function(e){var t=this.getUrl("users",e||{});return this.getJSON(t)}},{key:"getParentalRatings",value:function(){var e=this.getUrl("Localization/ParentalRatings");return this.getJSON(e)}},{key:"getDefaultImageQuality",value:function(e){return"backdrop"===e.toLowerCase()?80:90}},{key:"getUserImageUrl",value:function(e,t){if(!e)throw new Error("null userId");t=t||{};var r="Users/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),x(this,t),delete t.type,delete t.index,this.getUrl(r,t)}},{key:"getImageUrl",value:function(e,t){if(!e)throw new Error("itemId cannot be empty");t=t||{};var r="Items/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),t.quality=t.quality||this.getDefaultImageQuality(t.type),this.normalizeImageOptions&&this.normalizeImageOptions(t),delete t.type,delete t.index,this.getUrl(r,t)}},{key:"getScaledImageUrl",value:function(e,t){if(!e)throw new Error("itemId cannot be empty");t=t||{};var r="Items/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),x(this,t),delete t.type,delete t.index,delete t.minScale,this.getUrl(r,t)}},{key:"getThumbImageUrl",value:function(e,t){if(!e)throw new Error("null item");return(t=t||{}).imageType="thumb",e.ImageTags&&e.ImageTags.Thumb?(t.tag=e.ImageTags.Thumb,this.getImageUrl(e.Id,t)):e.ParentThumbItemId?(t.tag=e.ImageTags.ParentThumbImageTag,this.getImageUrl(e.ParentThumbItemId,t)):null}},{key:"updateUserPassword",value:function(e,t,r){if(!e)return Promise.reject();var n=this.getUrl("Users/".concat(e,"/Password"));return this.ajax({type:"POST",url:n,data:JSON.stringify({CurrentPw:t||"",NewPw:r}),contentType:"application/json"})}},{key:"updateEasyPassword",value:function(e,t){if(e){var r=this.getUrl("Users/".concat(e,"/EasyPassword"));return this.ajax({type:"POST",url:r,data:{NewPw:t}})}Promise.reject()}},{key:"resetUserPassword",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/Password")),r={resetPassword:!0};return this.ajax({type:"POST",url:t,data:r})}},{key:"resetEasyPassword",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/EasyPassword")),r={resetPassword:!0};return this.ajax({type:"POST",url:t,data:r})}},{key:"updateServerConfiguration",value:function(e){if(!e)throw new Error("null configuration");var t=this.getUrl("System/Configuration");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateNamedConfiguration",value:function(e,t){if(!t)throw new Error("null configuration");var r=this.getUrl("System/Configuration/".concat(e));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateItem",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("Items/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updatePluginSecurityInfo",value:function(e){var t=this.getUrl("Plugins/SecurityInfo");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"createUser",value:function(e){var t=this.getUrl("Users/New");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json",headers:{accept:"application/json"}})}},{key:"updateUser",value:function(e){if(!e)throw new Error("null user");var t=this.getUrl("Users/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateUserPolicy",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null policy");var r=this.getUrl("Users/".concat(e,"/Policy"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateUserConfiguration",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null configuration");var r=this.getUrl("Users/".concat(e,"/Configuration"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateScheduledTaskTriggers",value:function(e,t){if(!e)throw new Error("null id");if(!t)throw new Error("null triggers");var r=this.getUrl("ScheduledTasks/".concat(e,"/Triggers"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updatePluginConfiguration",value:function(e,t){if(!e)throw new Error("null Id");if(!t)throw new Error("null configuration");var r=this.getUrl("Plugins/".concat(e,"/Configuration"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"getAncestorItems",value:function(e,t){if(!e)throw new Error("null itemId");var r={};t&&(r.userId=t);var n=this.getUrl("Items/".concat(e,"/Ancestors"),r);return this.getJSON(n)}},{key:"getItems",value:function(e,t){var r;return r="string"===c(e).toString().toLowerCase()?this.getUrl("Users/".concat(e,"/Items"),t):this.getUrl("Items",t),this.getJSON(r)}},{key:"getResumableItems",value:function(e,t){return this.isMinServerVersion("3.2.33")?this.getJSON(this.getUrl("Users/".concat(e,"/Items/Resume"),t)):this.getItems(e,Object.assign({SortBy:"DatePlayed",SortOrder:"Descending",Filters:"IsResumable",Recursive:!0,CollapseBoxSetItems:!1,ExcludeLocationTypes:"Virtual"},t))}},{key:"getMovieRecommendations",value:function(e){return this.getJSON(this.getUrl("Movies/Recommendations",e))}},{key:"getUpcomingEpisodes",value:function(e){return this.getJSON(this.getUrl("Shows/Upcoming",e))}},{key:"getUserViews",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0,r=this.getUrl("Users/".concat(t||this.getCurrentUserId(),"/Views"),e);return this.getJSON(r)}},{key:"getArtists",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Artists",t);return this.getJSON(r)}},{key:"getAlbumArtists",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Artists/AlbumArtists",t);return this.getJSON(r)}},{key:"getGenres",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Genres",t);return this.getJSON(r)}},{key:"getMusicGenres",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("MusicGenres",t);return this.getJSON(r)}},{key:"getPeople",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Persons",t);return this.getJSON(r)}},{key:"getStudios",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Studios",t);return this.getJSON(r)}},{key:"getLocalTrailers",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/LocalTrailers"));return this.getJSON(r)}},{key:"getAdditionalVideoParts",value:function(e,t){if(!t)throw new Error("null itemId");var r={};e&&(r.userId=e);var n=this.getUrl("Videos/".concat(t,"/AdditionalParts"),r);return this.getJSON(n)}},{key:"getThemeMedia",value:function(e,t,r){if(!t)throw new Error("null itemId");var n={};e&&(n.userId=e),n.InheritFromParent=r||!1;var i=this.getUrl("Items/".concat(t,"/ThemeMedia"),n);return this.getJSON(i)}},{key:"getSearchHints",value:function(e){var t=this.getUrl("Search/Hints",e),r=this.serverId();return this.getJSON(t).then((function(e){return e.SearchHints.forEach((function(e){e.ServerId=r})),e}))}},{key:"getSpecialFeatures",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/SpecialFeatures"));return this.getJSON(r)}},{key:"getDateParamValue",value:function(e){function t(e){return e<10?"0".concat(e):e}var r=e;return"".concat(r.getFullYear()).concat(t(r.getMonth()+1)).concat(t(r.getDate())).concat(t(r.getHours())).concat(t(r.getMinutes())).concat(t(r.getSeconds()))}},{key:"markPlayed",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n={};r&&(n.DatePlayed=this.getDateParamValue(r));var i=this.getUrl("Users/".concat(e,"/PlayedItems/").concat(t),n);return this.ajax({type:"POST",url:i,dataType:"json"})}},{key:"markUnplayed",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/PlayedItems/").concat(t));return this.ajax({type:"DELETE",url:r,dataType:"json"})}},{key:"updateFavoriteStatus",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n=this.getUrl("Users/".concat(e,"/FavoriteItems/").concat(t)),i=r?"POST":"DELETE";return this.ajax({type:i,url:n,dataType:"json"})}},{key:"updateUserItemRating",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/Rating"),{likes:r});return this.ajax({type:"POST",url:n,dataType:"json"})}},{key:"getItemCounts",value:function(e){var t={};e&&(t.userId=e);var r=this.getUrl("Items/Counts",t);return this.getJSON(r)}},{key:"clearUserItemRating",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/Rating"));return this.ajax({type:"DELETE",url:r,dataType:"json"})}},{key:"reportPlaybackStart",value:function(e){if(!e)throw new Error("null options");this.lastPlaybackProgressReport=0,this.lastPlaybackProgressReportTicks=null,g(this),w(this);var t=this.getUrl("Sessions/Playing");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"reportPlaybackProgress",value:function(e){if(!e)throw new Error("null options");var t=e.EventName||"timeupdate",r=h[t]||0,n=(new Date).getTime()-(this.lastPlaybackProgressReport||0),i=e.PositionTicks;if(n=5e7&&(r=0)}if(r<(void 0!==this.reportPlaybackProgressTimeout?this.reportPlaybackProgressTimeout:1e6)&&w(this),this.lastPlaybackProgressOptions=e,this.reportPlaybackProgressPromise)return Promise.resolve();var a,s=this,u=!1,l=function(){s.reportPlaybackProgressPromise===a&&(delete s.lastPlaybackProgressOptions,delete s.reportPlaybackProgressTimeout,delete s.reportPlaybackProgressPromise,delete s.reportPlaybackProgressCancel)},c=Math.max(0,r-n);return a=new Promise((function(e,t){return setTimeout(e,c)})).then((function(){return u?Promise.resolve():function(e){if(l(),!e)throw new Error("null options");s.lastPlaybackProgressReport=(new Date).getTime(),s.lastPlaybackProgressReportTicks=e.PositionTicks;var t=s.getUrl("Sessions/Playing/Progress");return s.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}(s.lastPlaybackProgressOptions)})).finally((function(){l()})),this.reportPlaybackProgressTimeout=r,this.reportPlaybackProgressPromise=a,this.reportPlaybackProgressCancel=function(){u=!0,l()},a}},{key:"reportOfflineActions",value:function(e){if(!e)throw new Error("null actions");var t=this.getUrl("Sync/OfflineActions");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"syncData",value:function(e){if(!e)throw new Error("null data");var t=this.getUrl("Sync/Data");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t,dataType:"json"})}},{key:"getReadySyncItems",value:function(e){if(!e)throw new Error("null deviceId");var t=this.getUrl("Sync/Items/Ready",{TargetId:e});return this.getJSON(t)}},{key:"reportSyncJobItemTransferred",value:function(e){if(!e)throw new Error("null syncJobItemId");var t=this.getUrl("Sync/JobItems/".concat(e,"/Transferred"));return this.ajax({type:"POST",url:t})}},{key:"cancelSyncItems",value:function(e,t){if(!e)throw new Error("null itemIds");var r=this.getUrl("Sync/".concat(t||this.deviceId(),"/Items"),{ItemIds:e.join(",")});return this.ajax({type:"DELETE",url:r})}},{key:"reportPlaybackStopped",value:function(e){if(!e)throw new Error("null options");this.lastPlaybackProgressReport=0,this.lastPlaybackProgressReportTicks=null,v(this),w(this);var t=this.getUrl("Sessions/Playing/Stopped");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"sendPlayCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null options");var r=this.getUrl("Sessions/".concat(e,"/Playing"),t);return this.ajax({type:"POST",url:r})}},{key:"sendCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null command");var r={type:"POST",url:this.getUrl("Sessions/".concat(e,"/Command"))};return r.data=JSON.stringify(t),r.contentType="application/json",this.ajax(r)}},{key:"sendMessageCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null options");var r={type:"POST",url:this.getUrl("Sessions/".concat(e,"/Message"))};return r.data=JSON.stringify(t),r.contentType="application/json",this.ajax(r)}},{key:"sendPlayStateCommand",value:function(e,t,r){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null command");var n=this.getUrl("Sessions/".concat(e,"/Playing/").concat(t),r||{});return this.ajax({type:"POST",url:n})}},{key:"getSyncPlayGroups",value:function(){var e=this.getUrl("SyncPlay/List");return this.ajax({type:"GET",url:e})}},{key:"createSyncPlayGroup",value:function(){var e=this.getUrl("SyncPlay/New");return this.ajax({type:"POST",url:e})}},{key:"joinSyncPlayGroup",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Join",e);return this.ajax({type:"POST",url:t})}},{key:"leaveSyncPlayGroup",value:function(){var e=this.getUrl("SyncPlay/Leave");return this.ajax({type:"POST",url:e})}},{key:"sendSyncPlayPing",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Ping",e);return this.ajax({type:"POST",url:t})}},{key:"requestSyncPlayStart",value:function(){var e=this.getUrl("SyncPlay/Play");return this.ajax({type:"POST",url:e})}},{key:"requestSyncPlayPause",value:function(){var e=this.getUrl("SyncPlay/Pause");return this.ajax({type:"POST",url:e})}},{key:"requestSyncPlaySeek",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Seek",e);return this.ajax({type:"POST",url:t})}},{key:"createPackageReview",value:function(e){var t=this.getUrl("Packages/Reviews/".concat(e.id),e);return this.ajax({type:"POST",url:t})}},{key:"getPackageReviews",value:function(e,t,r,n){if(!e)throw new Error("null packageId");var i={};t&&(i.MinRating=t),r&&(i.MaxRating=r),n&&(i.Limit=n);var o=this.getUrl("Packages/".concat(e,"/Reviews"),i);return this.getJSON(o)}},{key:"getSavedEndpointInfo",value:function(){return this._endPointInfo}},{key:"getEndpointInfo",value:function(){var e=this._endPointInfo;if(e)return Promise.resolve(e);var t=this;return this.getJSON(this.getUrl("System/Endpoint")).then((function(e){return k(t,e),e}))}},{key:"getLatestItems",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.getJSON(this.getUrl("Users/".concat(this.getCurrentUserId(),"/Items/Latest"),e))}},{key:"getFilters",value:function(e){return this.getJSON(this.getUrl("Items/Filters2",e))}},{key:"setSystemInfo",value:function(e){this._serverVersion=e.Version}},{key:"serverVersion",value:function(){return this._serverVersion}},{key:"isMinServerVersion",value:function(e){var t=this.serverVersion();return!!t&&function(e,t){e=e.split("."),t=t.split(".");for(var r=0,n=Math.max(e.length,t.length);ro)return 1}return 0}(t,e)>=0}},{key:"handleMessageReceived",value:function(e){b(this,e)}}])&&d(t.prototype,r),n&&d(t,n),e}();function J(e){return(J="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function R(e,t){for(var r=0;rt.length&&0===e.indexOf(t))}function H(e,t){return q(e,t)?e.substr(t.length):e}function z(e){return e?V(e)?e:"local:".concat(e):null}function K(e){e.Id=z(e.Id),e.SeriesId=z(e.SeriesId),e.SeasonId=z(e.SeasonId),e.AlbumId=z(e.AlbumId),e.ParentId=z(e.ParentId),e.ParentThumbItemId=z(e.ParentThumbItemId),e.ParentPrimaryImageItemId=z(e.ParentPrimaryImageItemId),e.PrimaryImageItemId=z(e.PrimaryImageItemId),e.ParentLogoItemId=z(e.ParentLogoItemId),e.ParentBackdropItemId=z(e.ParentBackdropItemId),e.ParentBackdropImageTags=null}function Q(e,t,r){return e.getLocalFolders(t,r).then((function(r){var n=null;return r.length>0&&(n={Name:e.downloadsTitleText||"Downloads",ServerId:t,Id:"localview",Type:"localview",IsFolder:!0}),Promise.resolve(n)}))}var X=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&D(e,t)}(o,e);var t,r,n,i=M(o);function o(e,t,r,n,a,s,u){var l;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,o),(l=i.call(this,e,t,r,n,a,s)).localAssetManager=u,l}return t=o,(r=[{key:"getPlaybackInfo",value:function(e,t,r){var n=function(){return C.prototype.getPlaybackInfo.call(i,e,t,r)};if(V(e))return this.localAssetManager.getLocalItem(this.serverId(),W(e)).then((function(e){return{MediaSources:e.Item.MediaSources.map((function(e){return e.SupportsDirectPlay=!0,e.SupportsDirectStream=!1,e.SupportsTranscoding=!1,e.IsLocal=!0,e}))}}),n);var i=this;return this.localAssetManager.getLocalItem(this.serverId(),e).then((function(o){if(o){var a=o.Item.MediaSources.map((function(e){return e.SupportsDirectPlay=!0,e.SupportsDirectStream=!1,e.SupportsTranscoding=!1,e.IsLocal=!0,e}));return i.localAssetManager.fileExists(o.LocalPath).then((function(n){if(n){var o={MediaSources:a};return Promise.resolve(o)}return C.prototype.getPlaybackInfo.call(i,e,t,r)}),n)}return C.prototype.getPlaybackInfo.call(i,e,t,r)}),n)}},{key:"getItems",value:function(e,t){var r,n=this.serverInfo();if(n&&"localview"===t.ParentId)return this.getLocalFolders(n.Id,e).then((function(e){var t={Items:e,TotalRecordCount:e.length};return Promise.resolve(t)}));if(n&&t&&(V(t.ParentId)||V(t.SeriesId)||V(t.SeasonId)||G(t.ParentId)||V(t.AlbumIds)))return this.localAssetManager.getViewItems(n.Id,e,t).then((function(e){e.forEach((function(e){K(e)}));var t={Items:e,TotalRecordCount:e.length};return Promise.resolve(t)}));if(t&&t.ExcludeItemIds&&t.ExcludeItemIds.length){var i=t.ExcludeItemIds.split(",");for(r=0;r0?Promise.resolve(r[0]):Promise.reject()})):V(t)&&(r=this.serverInfo())?this.localAssetManager.getLocalItem(r.Id,W(t)).then((function(e){return K(e.Item),Promise.resolve(e.Item)})):C.prototype.getItem.call(this,e,t)}},{key:"getLocalFolders",value:function(e){var t=this.serverInfo();return e=e||t.UserId,this.localAssetManager.getViews(t.Id,e)}},{key:"getNextUpEpisodes",value:function(e){return e.SeriesId&&V(e.SeriesId)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getNextUpEpisodes.call(this,e)}},{key:"getSeasons",value:function(e,t){return V(e)?(t.SeriesId=e,t.IncludeItemTypes="Season",this.getItems(this.getCurrentUserId(),t)):C.prototype.getSeasons.call(this,e,t)}},{key:"getEpisodes",value:function(e,t){return V(t.SeasonId)||V(t.seasonId)||V(e)?(t.SeriesId=e,t.IncludeItemTypes="Episode",this.getItems(this.getCurrentUserId(),t)):C.prototype.getEpisodes.call(this,e,t)}},{key:"getLatestOfflineItems",value:function(e){e.SortBy="DateCreated",e.SortOrder="Descending";var t=this.serverInfo();return t?this.localAssetManager.getViewItems(t.Id,null,e).then((function(e){return e.forEach((function(e){K(e)})),Promise.resolve(e)})):Promise.resolve([])}},{key:"getThemeMedia",value:function(e,t,r){return G(t)||V(t)||B(t)?Promise.reject():C.prototype.getThemeMedia.call(this,e,t,r)}},{key:"getSpecialFeatures",value:function(e,t){return V(t)?Promise.resolve([]):C.prototype.getSpecialFeatures.call(this,e,t)}},{key:"getSimilarItems",value:function(e,t){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getSimilarItems.call(this,e,t)}},{key:"updateFavoriteStatus",value:function(e,t,r){return V(t)?Promise.resolve():C.prototype.updateFavoriteStatus.call(this,e,t,r)}},{key:"getScaledImageUrl",value:function(e,t){if(V(e)||t&&t.itemid&&V(t.itemid)){var r=this.serverInfo(),n=W(e);return this.localAssetManager.getImageUrl(r.Id,n,t)}return C.prototype.getScaledImageUrl.call(this,e,t)}},{key:"reportPlaybackStart",value:function(e){if(!e)throw new Error("null options");return V(e.ItemId)?Promise.resolve():C.prototype.reportPlaybackStart.call(this,e)}},{key:"reportPlaybackProgress",value:function(e){if(!e)throw new Error("null options");if(V(e.ItemId)){var t=this.serverInfo();if(t){var r=this;return this.localAssetManager.getLocalItem(t.Id,W(e.ItemId)).then((function(t){var n=t.Item;return"Video"===n.MediaType||"AudioBook"===n.Type?(n.UserData=n.UserData||{},n.UserData.PlaybackPositionTicks=e.PositionTicks,n.UserData.PlayedPercentage=Math.min(n.RunTimeTicks?(e.PositionTicks||0)/n.RunTimeTicks*100:0,100),r.localAssetManager.addOrUpdateLocalItem(t)):Promise.resolve()}))}return Promise.resolve()}return C.prototype.reportPlaybackProgress.call(this,e)}},{key:"reportPlaybackStopped",value:function(e){if(!e)throw new Error("null options");if(V(e.ItemId)){var t=this.serverInfo(),r={Date:(new Date).getTime(),ItemId:W(e.ItemId),PositionTicks:e.PositionTicks,ServerId:t.Id,Type:0,UserId:this.getCurrentUserId()};return this.localAssetManager.recordUserAction(r)}return C.prototype.reportPlaybackStopped.call(this,e)}},{key:"getIntros",value:function(e){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getIntros.call(this,e)}},{key:"getInstantMixFromItem",value:function(e,t){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getInstantMixFromItem.call(this,e,t)}},{key:"getItemDownloadUrl",value:function(e){if(V(e)){var t=this.serverInfo();if(t)return this.localAssetManager.getLocalItem(t.Id,W(e)).then((function(e){return Promise.resolve(e.LocalPath)}))}return C.prototype.getItemDownloadUrl.call(this,e)}}])&&R(t.prototype,r),n&&R(t,n),o}(C);function Y(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};!1!==t.reportCapabilities&&e.reportCapabilities(s),e.enableAutomaticBitrateDetection=t.enableAutomaticBitrateDetection,!1!==t.enableWebSocket&&(console.log("calling apiClient.ensureWebSocket"),e.ensureWebSocket())}function d(e,t,r){return u._getOrAddApiClient(e,t),(u.onLocalUserSignedIn?u.onLocalUserSignedIn.call(u,r):Promise.resolve()).then((function(){i.trigger(u,"localusersignedin",[r])}))}function h(e,t){return ae({type:"GET",url:ie(t,"System/Info"),dataType:"json",headers:{"X-MediaBrowser-Token":e.AccessToken}}).then((function(t){return ne(e,t),Promise.resolve()}),(function(){return e.UserId=null,e.AccessToken=null,Promise.resolve()}))}function v(e){var t={serverId:(e.serverInfo()||{}).Id};return e.logout().then((function(){i.trigger(u,"localusersignedout",[t])}),(function(){i.trigger(u,"localusersignedout",[t])}))}function f(e){if(e.Address&&e.EndpointAddress){var t=e.EndpointAddress.split(":")[0],r=e.Address.split(":");if(r.length>1){var n=r[r.length-1];isNaN(parseInt(n))||(t+=":".concat(n))}return ue(t)}return null}function g(e){var t=[],r=[];return!e.manualAddressOnly&&e.LocalAddress&&-1===r.indexOf(e.LocalAddress)&&(t.push({url:e.LocalAddress,mode:Z,timeout:0}),r.push(t[t.length-1].url)),e.ManualAddress&&-1===r.indexOf(e.ManualAddress)&&(t.push({url:e.ManualAddress,mode:ee,timeout:100}),r.push(t[t.length-1].url)),!e.manualAddressOnly&&e.RemoteAddress&&-1===r.indexOf(e.RemoteAddress)&&(t.push({url:e.RemoteAddress,mode:$,timeout:200}),r.push(t[t.length-1].url)),console.log("tryReconnect: "+r.join("|")),new Promise((function(e,r){var n={};n.numAddresses=t.length,n.rejects=0,t.map((function(t){setTimeout((function(){n.resolved||function(e,t,r,n,i){console.log("getTryConnectPromise "+e),ae({url:ie(e,"system/info/public"),timeout:2e4,type:"GET",dataType:"json"}).then((function(i){r.resolved||(r.resolved=!0,console.log("Reconnect succeeded to "+e),n({url:e,connectionMode:t,data:i}))}),(function(){console.log("Reconnect failed to "+e),r.resolved||(r.rejects++,r.rejects>=r.numAddresses&&i())}))}(t.url,t.mode,n,e,r)}),t.timeout)}))}))}function p(e,t){var r={ManualAddress:e,LastConnectionMode:ee};return u.connectToServer(r,t).then((function(e){return"Unavailable"===e.State?Promise.reject():e}))}this._apiClients=[],u._minServerVersion="3.2.33",u.appVersion=function(){return n},u.appName=function(){return r},u.capabilities=function(){return s},u.deviceId=function(){return a},u.credentialProvider=function(){return t},u.getServerInfo=function(e){return t.credentials().Servers.filter((function(t){return t.Id===e}))[0]},u.getLastUsedServer=function(){var e=t.credentials().Servers;return e.sort((function(e,t){return(t.DateLastAccessed||0)-(e.DateLastAccessed||0)})),e.length?e[0]:null},u.addApiClient=function(e){u._apiClients.push(e);var r=t.credentials().Servers.filter((function(t){return le(t.ManualAddress,e.serverAddress())||le(t.LocalAddress,e.serverAddress())||le(t.RemoteAddress,e.serverAddress())})),n=r.length?r[0]:e.serverInfo();if(n.DateLastAccessed=(new Date).getTime(),n.LastConnectionMode=ee,n.ManualAddress=e.serverAddress(),e.manualAddressOnly&&(n.manualAddressOnly=!0),e.serverInfo(n),e.onAuthenticated=function(e,t){return l(e,t,{},!0)},!r.length){var o=t.credentials();o.Servers=[n],t.credentials(o)}i.trigger(u,"apiclientcreated",[e])},u.clearData=function(){console.log("connection manager clearing data");var e=t.credentials();e.Servers=[],t.credentials(e)},u._getOrAddApiClient=function(e,t){var s=u.getApiClient(e.Id);return s||(s=new C(t,r,n,o,a),u._apiClients.push(s),s.serverInfo(e),s.onAuthenticated=function(e,t){return l(e,t,{},!0)},i.trigger(u,"apiclientcreated",[s])),console.log("returning instance from getOrAddApiClient"),s},u.getOrCreateApiClient=function(e){var r=t.credentials().Servers.filter((function(t){return le(t.Id,e)}));if(!r.length)throw new Error("Server not found: ".concat(e));var n=r[0];return u._getOrAddApiClient(n,te(n,n.LastConnectionMode))},u.user=function(e){return new Promise((function(t,r){var n;e&&e.getCurrentUserId()&&e&&e.getCurrentUserId()&&e.getCurrentUser().then((function(e){var r=function(e){return e&&e.PrimaryImageTag?{url:u.getApiClient(e).getUserImageUrl(e.Id,{tag:e.PrimaryImageTag,type:"Primary"}),supportsParams:!0}:{url:null,supportsParams:!1}}(n=e);t({localUser:n,name:n?n.Name:null,imageUrl:r.url,supportsImageParams:r.supportsParams})}))}))},u.logout=function(){for(var e=[],r=0,n=u._apiClients.length;ro)return 1}return 0}(u.minServerVersion(),o.Version)?(console.log("minServerVersion requirement not met. Server version: "+o.Version),n({State:"ServerUpdateNeeded",Servers:[e]})):e.Id&&o.Id!==e.Id?(console.log("http request succeeded, but found a different server Id than what was expected"),re(0,n)):function e(r,n,o,a,s,l){var v=arguments.length>6&&void 0!==arguments[6]?arguments[6]:{},f=t.credentials();if(!1===v.enableAutoLogin)r.UserId=null,r.AccessToken=null;else if(r.AccessToken&&s)return void h(r,a).then((function(){e(r,n,o,a,!1,l,v)}));ne(r,n),r.LastConnectionMode=o,!1!==v.updateDateLastAccessed&&(r.DateLastAccessed=(new Date).getTime());t.addOrUpdateServer(f.Servers,r),t.credentials(f);var g={Servers:[]};g.ApiClient=u._getOrAddApiClient(r,a),g.ApiClient.setSystemInfo(n),g.State=r.AccessToken&&!1!==v.enableAutoLogin?"SignedIn":"ServerSignIn",g.Servers.push(r),g.ApiClient.enableAutomaticBitrateDetection=v.enableAutomaticBitrateDetection,g.ApiClient.updateServerInfo(r,a);var p=function(){l(g),i.trigger(u,"connected",[g])};"SignedIn"===g.State?(c(g.ApiClient,v),g.ApiClient.getCurrentUser().then((function(e){d(r,a,e).then(p,p)}),p)):p()}(e,o,s,a,!0,n,r)}),(function(){re(0,n)}))}))},u.connectToAddress=function(e,t){if(!e)return Promise.reject();e=ue(e);var r=[];/^[^:]+:\/\//.test(e)?r.push(e):(r.push("https://".concat(e)),r.push("http://".concat(e)));var n=0;return p(r[n],t).catch((function e(){return console.log("connectToAddress ".concat(r[n]," failed")),++n {\n c.apply(obj, eventArgs);\n });\n }\n};\n","function onCachePutFail(e) {\n console.log(e);\n}\n\nfunction updateCache(instance) {\n const cache = instance.cache;\n if (cache) {\n cache.put('data', new Response(JSON.stringify(instance.localData))).catch(onCachePutFail);\n }\n}\n\nfunction onCacheOpened(result) {\n this.cache = result;\n this.localData = {};\n}\n\nclass AppStore {\n constructor() {\n try {\n if (self.caches) {\n caches.open('embydata').then(onCacheOpened.bind(this));\n }\n } catch (err) {\n console.log(`Error opening cache: ${err}`);\n }\n }\n\n setItem(name, value) {\n localStorage.setItem(name, value);\n const localData = this.localData;\n if (localData) {\n const changed = localData[name] !== value;\n if (changed) {\n localData[name] = value;\n updateCache(this);\n }\n }\n }\n\n static getInstance() {\n if (!AppStore.instance) {\n AppStore.instance = new AppStore();\n }\n\n return AppStore.instance;\n }\n\n getItem(name) {\n return localStorage.getItem(name);\n }\n\n removeItem(name) {\n localStorage.removeItem(name);\n const localData = this.localData;\n if (localData) {\n localData[name] = null;\n delete localData[name];\n updateCache(this);\n }\n }\n}\n\nexport default AppStore.getInstance();\n","import events from './events';\nimport appStorage from './appStorage';\n\n/** Report rate limits in ms for different events */\nconst reportRateLimits = {\n timeupdate: 10000,\n volumechange: 3000\n};\n\nfunction redetectBitrate(instance) {\n stopBitrateDetection(instance);\n\n if (instance.accessToken() && instance.enableAutomaticBitrateDetection !== false) {\n setTimeout(redetectBitrateInternal.bind(instance), 6000);\n }\n}\n\nfunction redetectBitrateInternal() {\n if (this.accessToken()) {\n this.detectBitrate();\n }\n}\n\nfunction stopBitrateDetection(instance) {\n if (instance.detectTimeout) {\n clearTimeout(instance.detectTimeout);\n }\n}\n\nfunction replaceAll(originalString, strReplace, strWith) {\n const reg = new RegExp(strReplace, 'ig');\n return originalString.replace(reg, strWith);\n}\n\nfunction onFetchFail(instance, url, response) {\n events.trigger(instance, 'requestfail', [\n {\n url,\n status: response.status,\n errorCode: response.headers ? response.headers.get('X-Application-Error-Code') : null\n }\n ]);\n}\n\nfunction paramsToString(params) {\n const values = [];\n\n for (const key in params) {\n const value = params[key];\n\n if (value !== null && value !== undefined && value !== '') {\n values.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);\n }\n }\n return values.join('&');\n}\n\nfunction fetchWithTimeout(url, options, timeoutMs) {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(reject, timeoutMs);\n\n options = options || {};\n options.credentials = 'same-origin';\n\n fetch(url, options)\n .then((response) => {\n clearTimeout(timeout);\n resolve(response);\n })\n .catch((error) => {\n clearTimeout(timeout);\n reject(error);\n });\n });\n}\n\nfunction getFetchPromise(request) {\n const headers = request.headers || {};\n\n if (request.dataType === 'json') {\n headers.accept = 'application/json';\n }\n\n const fetchRequest = {\n headers,\n method: request.type,\n credentials: 'same-origin'\n };\n\n let contentType = request.contentType;\n\n if (request.data) {\n if (typeof request.data === 'string') {\n fetchRequest.body = request.data;\n } else {\n fetchRequest.body = paramsToString(request.data);\n\n contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';\n }\n }\n\n if (contentType) {\n headers['Content-Type'] = contentType;\n }\n\n if (!request.timeout) {\n return fetch(request.url, fetchRequest);\n }\n\n return fetchWithTimeout(request.url, fetchRequest, request.timeout);\n}\n\nfunction cancelReportPlaybackProgressPromise(instance) {\n if (typeof instance.reportPlaybackProgressCancel === 'function') instance.reportPlaybackProgressCancel();\n}\n\n/**\n * Creates a new api client instance\n * @param {String} serverAddress\n * @param {String} appName\n * @param {String} appVersion\n */\nclass ApiClient {\n constructor(serverAddress, appName, appVersion, deviceName, deviceId) {\n if (!serverAddress) {\n throw new Error('Must supply a serverAddress');\n }\n\n console.debug(`ApiClient serverAddress: ${serverAddress}`);\n console.debug(`ApiClient appName: ${appName}`);\n console.debug(`ApiClient appVersion: ${appVersion}`);\n console.debug(`ApiClient deviceName: ${deviceName}`);\n console.debug(`ApiClient deviceId: ${deviceId}`);\n\n this._serverInfo = {};\n this._serverAddress = serverAddress;\n this._deviceId = deviceId;\n this._deviceName = deviceName;\n this._appName = appName;\n this._appVersion = appVersion;\n }\n\n appName() {\n return this._appName;\n }\n\n setRequestHeaders(headers) {\n const currentServerInfo = this.serverInfo();\n const appName = this._appName;\n const accessToken = currentServerInfo.AccessToken;\n\n const values = [];\n\n if (appName) {\n values.push(`Client=\"${appName}\"`);\n }\n\n if (this._deviceName) {\n values.push(`Device=\"${this._deviceName}\"`);\n }\n\n if (this._deviceId) {\n values.push(`DeviceId=\"${this._deviceId}\"`);\n }\n\n if (this._appVersion) {\n values.push(`Version=\"${this._appVersion}\"`);\n }\n\n if (accessToken) {\n values.push(`Token=\"${accessToken}\"`);\n }\n\n if (values.length) {\n const auth = `MediaBrowser ${values.join(', ')}`;\n //headers.Authorization = auth;\n headers['X-Emby-Authorization'] = auth;\n }\n }\n\n appVersion() {\n return this._appVersion;\n }\n\n deviceName() {\n return this._deviceName;\n }\n\n deviceId() {\n return this._deviceId;\n }\n\n /**\n * Gets the server address.\n */\n serverAddress(val) {\n if (val != null) {\n if (val.toLowerCase().indexOf('http') !== 0) {\n throw new Error(`Invalid url: ${val}`);\n }\n\n const changed = val !== this._serverAddress;\n\n this._serverAddress = val;\n\n this.onNetworkChange();\n\n if (changed) {\n events.trigger(this, 'serveraddresschanged');\n }\n }\n\n return this._serverAddress;\n }\n\n onNetworkChange() {\n this.lastDetectedBitrate = 0;\n this.lastDetectedBitrateTime = 0;\n setSavedEndpointInfo(this, null);\n\n redetectBitrate(this);\n }\n\n /**\n * Creates an api url based on a handler name and query string parameters\n * @param {String} name\n * @param {Object} params\n */\n getUrl(name, params, serverAddress) {\n if (!name) {\n throw new Error('Url name cannot be empty');\n }\n\n let url = serverAddress || this._serverAddress;\n\n if (!url) {\n throw new Error('serverAddress is yet not set');\n }\n\n if (name.charAt(0) !== '/') {\n url += '/';\n }\n\n url += name;\n\n if (params) {\n params = paramsToString(params);\n if (params) {\n url += `?${params}`;\n }\n }\n\n return url;\n }\n\n fetchWithFailover(request, enableReconnection) {\n console.log(`Requesting ${request.url}`);\n\n request.timeout = 30000;\n const instance = this;\n\n return getFetchPromise(request)\n .then((response) => {\n instance.lastFetch = new Date().getTime();\n\n if (response.status < 400) {\n if (request.dataType === 'json' || request.headers.accept === 'application/json') {\n return response.json();\n } else if (\n request.dataType === 'text' ||\n (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0\n ) {\n return response.text();\n } else {\n return response;\n }\n } else {\n onFetchFail(instance, request.url, response);\n return Promise.reject(response);\n }\n })\n .catch((error) => {\n if (error) {\n console.log(`Request failed to ${request.url} ${error.toString()}`);\n } else {\n console.log(`Request timed out to ${request.url}`);\n }\n\n // http://api.jquery.com/jQuery.ajax/\n if ((!error || !error.status) && enableReconnection) {\n console.log('Attempting reconnection');\n\n const previousServerAddress = instance.serverAddress();\n\n return tryReconnect(instance)\n .then(() => {\n console.log('Reconnect succeesed');\n request.url = request.url.replace(previousServerAddress, instance.serverAddress());\n\n return instance.fetchWithFailover(request, false);\n })\n .catch((innerError) => {\n console.log('Reconnect failed');\n onFetchFail(instance, request.url, {});\n throw innerError;\n });\n } else {\n console.log('Reporting request failure');\n\n onFetchFail(instance, request.url, {});\n throw error;\n }\n });\n }\n\n /**\n * Wraps around jQuery ajax methods to add additional info to the request.\n */\n fetch(request, includeAuthorization) {\n if (!request) {\n return Promise.reject('Request cannot be null');\n }\n\n request.headers = request.headers || {};\n\n if (includeAuthorization !== false) {\n this.setRequestHeaders(request.headers);\n }\n\n if (this.enableAutomaticNetworking === false || request.type !== 'GET') {\n console.log(`Requesting url without automatic networking: ${request.url}`);\n\n const instance = this;\n return getFetchPromise(request)\n .then((response) => {\n instance.lastFetch = new Date().getTime();\n\n if (response.status < 400) {\n if (request.dataType === 'json' || request.headers.accept === 'application/json') {\n return response.json();\n } else if (\n request.dataType === 'text' ||\n (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0\n ) {\n return response.text();\n } else {\n return response;\n }\n } else {\n onFetchFail(instance, request.url, response);\n return Promise.reject(response);\n }\n })\n .catch((error) => {\n onFetchFail(instance, request.url, {});\n return Promise.reject(error);\n });\n }\n\n return this.fetchWithFailover(request, true);\n }\n\n setAuthenticationInfo(accessKey, userId) {\n this._currentUser = null;\n\n this._serverInfo.AccessToken = accessKey;\n this._serverInfo.UserId = userId;\n redetectBitrate(this);\n }\n\n serverInfo(info) {\n if (info) {\n this._serverInfo = info;\n }\n\n return this._serverInfo;\n }\n\n /**\n * Gets or sets the current user id.\n */\n getCurrentUserId() {\n return this._serverInfo.UserId;\n }\n\n accessToken() {\n return this._serverInfo.AccessToken;\n }\n\n serverId() {\n return this.serverInfo().Id;\n }\n\n serverName() {\n return this.serverInfo().Name;\n }\n\n /**\n * Wraps around jQuery ajax methods to add additional info to the request.\n */\n ajax(request, includeAuthorization) {\n if (!request) {\n return Promise.reject('Request cannot be null');\n }\n\n return this.fetch(request, includeAuthorization);\n }\n\n /**\n * Gets or sets the current user id.\n */\n getCurrentUser(enableCache) {\n if (this._currentUser) {\n return Promise.resolve(this._currentUser);\n }\n\n const userId = this.getCurrentUserId();\n\n if (!userId) {\n return Promise.reject();\n }\n\n const instance = this;\n let user;\n\n const serverPromise = this.getUser(userId)\n .then((userObject) => {\n appStorage.setItem(`user-${userObject.Id}-${userObject.ServerId}`, JSON.stringify(userObject));\n\n instance._currentUser = userObject;\n return userObject;\n })\n .catch((response) => {\n // if timed out, look for cached value\n if (!response.status) {\n if (userId && instance.accessToken()) {\n user = getCachedUser(instance, userId);\n if (user) {\n return Promise.resolve(user);\n }\n }\n }\n\n throw response;\n });\n\n if (!this.lastFetch && enableCache !== false) {\n user = getCachedUser(instance, userId);\n if (user) {\n return Promise.resolve(user);\n }\n }\n\n return serverPromise;\n }\n\n isLoggedIn() {\n const info = this.serverInfo();\n if (info) {\n if (info.UserId && info.AccessToken) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Logout current user\n */\n logout() {\n stopBitrateDetection(this);\n this.closeWebSocket();\n\n const done = () => {\n const info = this.serverInfo();\n if (info && info.UserId && info.Id) {\n appStorage.removeItem(`user-${info.UserId}-${info.Id}`);\n }\n this.setAuthenticationInfo(null, null);\n };\n\n if (this.accessToken()) {\n const url = this.getUrl('Sessions/Logout');\n\n return this.ajax({\n type: 'POST',\n url\n }).then(done, done);\n }\n\n done();\n return Promise.resolve();\n }\n\n /**\n * Authenticates a user\n * @param {String} name\n * @param {String} password\n */\n authenticateUserByName(name, password) {\n if (!name) {\n return Promise.reject();\n }\n\n const url = this.getUrl('Users/authenticatebyname');\n\n return new Promise((resolve, reject) => {\n const postData = {\n Username: name,\n Pw: password || ''\n };\n\n this.ajax({\n type: 'POST',\n url: url,\n data: JSON.stringify(postData),\n dataType: 'json',\n contentType: 'application/json'\n })\n .then((result) => {\n const afterOnAuthenticated = () => {\n redetectBitrate(this);\n resolve(result);\n };\n\n if (this.onAuthenticated) {\n this.onAuthenticated(this, result).then(afterOnAuthenticated);\n } else {\n afterOnAuthenticated();\n }\n })\n .catch(reject);\n });\n }\n\n ensureWebSocket() {\n if (this.isWebSocketOpenOrConnecting() || !this.isWebSocketSupported()) {\n return;\n }\n\n try {\n this.openWebSocket();\n } catch (err) {\n console.log(`Error opening web socket: ${err}`);\n }\n }\n\n openWebSocket() {\n const accessToken = this.accessToken();\n\n if (!accessToken) {\n throw new Error('Cannot open web socket without access token.');\n }\n\n let url = this.getUrl('socket');\n\n url = replaceAll(url, 'emby/socket', 'embywebsocket');\n url = replaceAll(url, 'https:', 'wss:');\n url = replaceAll(url, 'http:', 'ws:');\n\n url += `?api_key=${accessToken}`;\n url += `&deviceId=${this.deviceId()}`;\n\n console.log(`opening web socket with url: ${url}`);\n\n const webSocket = new WebSocket(url);\n\n webSocket.onmessage = onWebSocketMessage.bind(this);\n webSocket.onopen = onWebSocketOpen.bind(this);\n webSocket.onerror = onWebSocketError.bind(this);\n setSocketOnClose(this, webSocket);\n\n this._webSocket = webSocket;\n }\n\n closeWebSocket() {\n const socket = this._webSocket;\n\n if (socket && socket.readyState === WebSocket.OPEN) {\n socket.close();\n }\n }\n\n sendWebSocketMessage(name, data) {\n console.log(`Sending web socket message: ${name}`);\n\n let msg = { MessageType: name };\n\n if (data) {\n msg.Data = data;\n }\n\n msg = JSON.stringify(msg);\n\n this._webSocket.send(msg);\n }\n\n sendMessage(name, data) {\n if (this.isWebSocketOpen()) {\n this.sendWebSocketMessage(name, data);\n }\n }\n\n isMessageChannelOpen() {\n return this.isWebSocketOpen();\n }\n\n isWebSocketOpen() {\n const socket = this._webSocket;\n\n if (socket) {\n return socket.readyState === WebSocket.OPEN;\n }\n return false;\n }\n\n isWebSocketOpenOrConnecting() {\n const socket = this._webSocket;\n\n if (socket) {\n return socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING;\n }\n return false;\n }\n\n get(url) {\n return this.ajax({\n type: 'GET',\n url\n });\n }\n\n getJSON(url, includeAuthorization) {\n return this.fetch(\n {\n url,\n type: 'GET',\n dataType: 'json',\n headers: {\n accept: 'application/json'\n }\n },\n includeAuthorization\n );\n }\n\n updateServerInfo(server, serverUrl) {\n if (server == null) {\n throw new Error('server cannot be null');\n }\n\n this.serverInfo(server);\n\n if (!serverUrl) {\n throw new Error(`serverUrl cannot be null. serverInfo: ${JSON.stringify(server)}`);\n }\n console.log(`Setting server address to ${serverUrl}`);\n this.serverAddress(serverUrl);\n }\n\n isWebSocketSupported() {\n try {\n return WebSocket != null;\n } catch (err) {\n return false;\n }\n }\n\n clearAuthenticationInfo() {\n this.setAuthenticationInfo(null, null);\n }\n\n encodeName(name) {\n name = name.split('/').join('-');\n name = name.split('&').join('-');\n name = name.split('?').join('-');\n\n const val = paramsToString({ name });\n return val.substring(val.indexOf('=') + 1).replace(\"'\", '%27');\n }\n\n /**\n * Gets the server time as a UTC formatted string.\n * @returns {Promise} Promise that it's fulfilled on request completion.\n * @since 10.6.0\n */\n getServerTime() {\n const url = this.getUrl('GetUTCTime');\n\n return this.ajax({\n type: 'GET',\n url: url\n });\n }\n\n getDownloadSpeed(byteSize) {\n const url = this.getUrl('Playback/BitrateTest', {\n Size: byteSize\n });\n\n const now = new Date().getTime();\n\n return this.ajax({\n type: 'GET',\n url,\n timeout: 5000\n }).then(() => {\n const responseTimeSeconds = (new Date().getTime() - now) / 1000;\n const bytesPerSecond = byteSize / responseTimeSeconds;\n const bitrate = Math.round(bytesPerSecond * 8);\n\n return bitrate;\n });\n }\n\n detectBitrate(force) {\n if (\n !force &&\n this.lastDetectedBitrate &&\n new Date().getTime() - (this.lastDetectedBitrateTime || 0) <= 3600000\n ) {\n return Promise.resolve(this.lastDetectedBitrate);\n }\n\n const instance = this;\n\n return this.getEndpointInfo().then(\n (info) => detectBitrateWithEndpointInfo(instance, info),\n (info) => detectBitrateWithEndpointInfo(instance, {})\n );\n }\n\n /**\n * Gets an item from the server\n * Omit itemId to get the root folder.\n */\n getItem(userId, itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = userId ? this.getUrl(`Users/${userId}/Items/${itemId}`) : this.getUrl(`Items/${itemId}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the root folder from the server\n */\n getRootFolder(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/Root`);\n\n return this.getJSON(url);\n }\n\n getNotificationSummary(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Notifications/${userId}/Summary`);\n\n return this.getJSON(url);\n }\n\n getNotifications(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Notifications/${userId}`, options || {});\n\n return this.getJSON(url);\n }\n\n markNotificationsRead(userId, idList, isRead) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!idList) {\n throw new Error('null idList');\n }\n\n const suffix = isRead ? 'Read' : 'Unread';\n\n const params = {\n UserId: userId,\n Ids: idList.join(',')\n };\n\n const url = this.getUrl(`Notifications/${userId}/${suffix}`, params);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getRemoteImageProviders(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const urlPrefix = getRemoteImagePrefix(this, options);\n\n const url = this.getUrl(`${urlPrefix}/RemoteImages/Providers`, options);\n\n return this.getJSON(url);\n }\n\n getAvailableRemoteImages(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const urlPrefix = getRemoteImagePrefix(this, options);\n\n const url = this.getUrl(`${urlPrefix}/RemoteImages`, options);\n\n return this.getJSON(url);\n }\n\n downloadRemoteImage(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const urlPrefix = getRemoteImagePrefix(this, options);\n\n const url = this.getUrl(`${urlPrefix}/RemoteImages/Download`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getRecordingFolders(userId) {\n const url = this.getUrl('LiveTv/Recordings/Folders', { userId: userId });\n\n return this.getJSON(url);\n }\n\n getLiveTvInfo(options) {\n const url = this.getUrl('LiveTv/Info', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvGuideInfo(options) {\n const url = this.getUrl('LiveTv/GuideInfo', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvChannel(id, userId) {\n if (!id) {\n throw new Error('null id');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`LiveTv/Channels/${id}`, options);\n\n return this.getJSON(url);\n }\n\n getLiveTvChannels(options) {\n const url = this.getUrl('LiveTv/Channels', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvPrograms(options = {}) {\n if (options.channelIds && options.channelIds.length > 1800) {\n return this.ajax({\n type: 'POST',\n url: this.getUrl('LiveTv/Programs'),\n data: JSON.stringify(options),\n contentType: 'application/json',\n dataType: 'json'\n });\n } else {\n return this.ajax({\n type: 'GET',\n url: this.getUrl('LiveTv/Programs', options),\n dataType: 'json'\n });\n }\n }\n\n getLiveTvRecommendedPrograms(options = {}) {\n return this.ajax({\n type: 'GET',\n url: this.getUrl('LiveTv/Programs/Recommended', options),\n dataType: 'json'\n });\n }\n\n getLiveTvRecordings(options) {\n const url = this.getUrl('LiveTv/Recordings', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvRecordingSeries(options) {\n const url = this.getUrl('LiveTv/Recordings/Series', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvRecordingGroups(options) {\n const url = this.getUrl('LiveTv/Recordings/Groups', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvRecordingGroup(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Recordings/Groups/${id}`);\n\n return this.getJSON(url);\n }\n\n getLiveTvRecording(id, userId) {\n if (!id) {\n throw new Error('null id');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`LiveTv/Recordings/${id}`, options);\n\n return this.getJSON(url);\n }\n\n getLiveTvProgram(id, userId) {\n if (!id) {\n throw new Error('null id');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`LiveTv/Programs/${id}`, options);\n\n return this.getJSON(url);\n }\n\n deleteLiveTvRecording(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Recordings/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n cancelLiveTvTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Timers/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n getLiveTvTimers(options) {\n const url = this.getUrl('LiveTv/Timers', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Timers/${id}`);\n\n return this.getJSON(url);\n }\n\n getNewLiveTvTimerDefaults(options = {}) {\n const url = this.getUrl('LiveTv/Timers/Defaults', options);\n\n return this.getJSON(url);\n }\n\n createLiveTvTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl('LiveTv/Timers');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n updateLiveTvTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl(`LiveTv/Timers/${item.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n resetLiveTvTuner(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Tuners/${id}/Reset`);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getLiveTvSeriesTimers(options) {\n const url = this.getUrl('LiveTv/SeriesTimers', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvSeriesTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/SeriesTimers/${id}`);\n\n return this.getJSON(url);\n }\n\n cancelLiveTvSeriesTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/SeriesTimers/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n createLiveTvSeriesTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl('LiveTv/SeriesTimers');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n updateLiveTvSeriesTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl(`LiveTv/SeriesTimers/${item.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n getRegistrationInfo(feature) {\n const url = this.getUrl(`Registrations/${feature}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server status\n */\n getSystemInfo(itemId) {\n const url = this.getUrl('System/Info');\n\n const instance = this;\n\n return this.getJSON(url).then((info) => {\n instance.setSystemInfo(info);\n return Promise.resolve(info);\n });\n }\n\n getSyncStatus() {\n const url = this.getUrl('Sync/' + itemId + '/Status');\n\n return this.ajax({\n url: url,\n type: 'POST',\n dataType: 'json',\n contentType: 'application/json',\n data: JSON.stringify({\n TargetId: this.deviceId()\n })\n });\n }\n\n /**\n * Gets the current server status\n */\n getPublicSystemInfo() {\n const url = this.getUrl('System/Info/Public');\n\n const instance = this;\n\n return this.getJSON(url).then((info) => {\n instance.setSystemInfo(info);\n return Promise.resolve(info);\n });\n }\n\n getInstantMixFromItem(itemId, options) {\n const url = this.getUrl(`Items/${itemId}/InstantMix`, options);\n\n return this.getJSON(url);\n }\n\n getEpisodes(itemId, options) {\n const url = this.getUrl(`Shows/${itemId}/Episodes`, options);\n\n return this.getJSON(url);\n }\n\n getDisplayPreferences(id, userId, app) {\n const url = this.getUrl(`DisplayPreferences/${id}`, {\n userId,\n client: app\n });\n\n return this.getJSON(url);\n }\n\n updateDisplayPreferences(id, obj, userId, app) {\n const url = this.getUrl(`DisplayPreferences/${id}`, {\n userId,\n client: app\n });\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(obj),\n contentType: 'application/json'\n });\n }\n\n getSeasons(itemId, options) {\n const url = this.getUrl(`Shows/${itemId}/Seasons`, options);\n\n return this.getJSON(url);\n }\n\n getSimilarItems(itemId, options) {\n const url = this.getUrl(`Items/${itemId}/Similar`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all cultures known to the server\n */\n getCultures() {\n const url = this.getUrl('Localization/cultures');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all countries known to the server\n */\n getCountries() {\n const url = this.getUrl('Localization/countries');\n\n return this.getJSON(url);\n }\n\n getPlaybackInfo(itemId, options, deviceProfile) {\n const postData = {\n DeviceProfile: deviceProfile\n };\n\n return this.ajax({\n url: this.getUrl(`Items/${itemId}/PlaybackInfo`, options),\n type: 'POST',\n data: JSON.stringify(postData),\n contentType: 'application/json',\n dataType: 'json'\n });\n }\n\n getLiveStreamMediaInfo(liveStreamId) {\n const postData = {\n LiveStreamId: liveStreamId\n };\n\n return this.ajax({\n url: this.getUrl('LiveStreams/MediaInfo'),\n type: 'POST',\n data: JSON.stringify(postData),\n contentType: 'application/json',\n dataType: 'json'\n });\n }\n\n getIntros(itemId) {\n return this.getJSON(this.getUrl(`Users/${this.getCurrentUserId()}/Items/${itemId}/Intros`));\n }\n\n /**\n * Gets the directory contents of a path on the server\n */\n getDirectoryContents(path, options) {\n if (!path) {\n throw new Error('null path');\n }\n if (typeof path !== 'string') {\n throw new Error('invalid path');\n }\n\n options = options || {};\n\n options.path = path;\n\n const url = this.getUrl('Environment/DirectoryContents', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets shares from a network device\n */\n getNetworkShares(path) {\n if (!path) {\n throw new Error('null path');\n }\n\n const options = {};\n options.path = path;\n\n const url = this.getUrl('Environment/NetworkShares', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the parent of a given path\n */\n getParentPath(path) {\n if (!path) {\n throw new Error('null path');\n }\n\n const options = {};\n options.path = path;\n\n const url = this.getUrl('Environment/ParentPath', options);\n\n return this.ajax({\n type: 'GET',\n url,\n dataType: 'text'\n });\n }\n\n /**\n * Gets a list of physical drives from the server\n */\n getDrives() {\n const url = this.getUrl('Environment/Drives');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a list of network devices from the server\n */\n getNetworkDevices() {\n const url = this.getUrl('Environment/NetworkDevices');\n\n return this.getJSON(url);\n }\n\n /**\n * Cancels a package installation\n */\n cancelPackageInstallation(installationId) {\n if (!installationId) {\n throw new Error('null installationId');\n }\n\n const url = this.getUrl(`Packages/Installing/${installationId}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Refreshes metadata for an item\n */\n refreshItem(itemId, options) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Items/${itemId}/Refresh`, options || {});\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Installs or updates a new plugin\n */\n installPlugin(name, guid, version) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {\n AssemblyGuid: guid\n };\n\n if (version) {\n options.version = version;\n }\n\n const url = this.getUrl(`Packages/Installed/${name}`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Instructs the server to perform a restart.\n */\n restartServer() {\n const url = this.getUrl('System/Restart');\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Instructs the server to perform a shutdown.\n */\n shutdownServer() {\n const url = this.getUrl('System/Shutdown');\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Gets information about an installable package\n */\n getPackageInfo(name, guid) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {\n AssemblyGuid: guid\n };\n\n const url = this.getUrl(`Packages/${name}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the virtual folder list\n */\n getVirtualFolders() {\n let url = 'Library/VirtualFolders';\n\n url = this.getUrl(url);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all the paths of the locations in the physical root.\n */\n getPhysicalPaths() {\n const url = this.getUrl('Library/PhysicalPaths');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server configuration\n */\n getServerConfiguration() {\n const url = this.getUrl('System/Configuration');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server configuration\n */\n getDevicesOptions() {\n const url = this.getUrl('System/Configuration/devices');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server configuration\n */\n getContentUploadHistory() {\n const url = this.getUrl('Devices/CameraUploads', {\n DeviceId: this.deviceId()\n });\n\n return this.getJSON(url);\n }\n\n getNamedConfiguration(name) {\n const url = this.getUrl(`System/Configuration/${name}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the server's scheduled tasks\n */\n getScheduledTasks(options = {}) {\n const url = this.getUrl('ScheduledTasks', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Starts a scheduled task\n */\n startScheduledTask(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`ScheduledTasks/Running/${id}`);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Gets a scheduled task\n */\n getScheduledTask(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`ScheduledTasks/${id}`);\n\n return this.getJSON(url);\n }\n\n getNextUpEpisodes(options) {\n const url = this.getUrl('Shows/NextUp', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Stops a scheduled task\n */\n stopScheduledTask(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`ScheduledTasks/Running/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Gets the configuration of a plugin\n * @param {String} Id\n */\n getPluginConfiguration(id) {\n if (!id) {\n throw new Error('null Id');\n }\n\n const url = this.getUrl(`Plugins/${id}/Configuration`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a list of plugins that are available to be installed\n */\n getAvailablePlugins(options = {}) {\n options.PackageType = 'UserInstalled';\n\n const url = this.getUrl('Packages', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Uninstalls a plugin\n * @param {String} Id\n */\n uninstallPlugin(id) {\n if (!id) {\n throw new Error('null Id');\n }\n\n const url = this.getUrl(`Plugins/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Removes a virtual folder\n * @param {String} name\n */\n removeVirtualFolder(name, refreshLibrary) {\n if (!name) {\n throw new Error('null name');\n }\n\n let url = 'Library/VirtualFolders';\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false,\n name\n });\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Adds a virtual folder\n * @param {String} name\n */\n addVirtualFolder(name, type, refreshLibrary, libraryOptions) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (type) {\n options.collectionType = type;\n }\n\n options.refreshLibrary = refreshLibrary ? true : false;\n options.name = name;\n\n let url = 'Library/VirtualFolders';\n\n url = this.getUrl(url, options);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n LibraryOptions: libraryOptions\n }),\n contentType: 'application/json'\n });\n }\n\n updateVirtualFolderOptions(id, libraryOptions) {\n if (!id) {\n throw new Error('null name');\n }\n\n let url = 'Library/VirtualFolders/LibraryOptions';\n\n url = this.getUrl(url);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n Id: id,\n LibraryOptions: libraryOptions\n }),\n contentType: 'application/json'\n });\n }\n\n /**\n * Renames a virtual folder\n * @param {String} name\n */\n renameVirtualFolder(name, newName, refreshLibrary) {\n if (!name) {\n throw new Error('null name');\n }\n\n let url = 'Library/VirtualFolders/Name';\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false,\n newName,\n name\n });\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Adds an additional mediaPath to an existing virtual folder\n * @param {String} name\n */\n addMediaPath(virtualFolderName, mediaPath, networkSharePath, refreshLibrary) {\n if (!virtualFolderName) {\n throw new Error('null virtualFolderName');\n }\n\n if (!mediaPath) {\n throw new Error('null mediaPath');\n }\n\n let url = 'Library/VirtualFolders/Paths';\n\n const pathInfo = {\n Path: mediaPath\n };\n if (networkSharePath) {\n pathInfo.NetworkPath = networkSharePath;\n }\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false\n });\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n Name: virtualFolderName,\n PathInfo: pathInfo\n }),\n contentType: 'application/json'\n });\n }\n\n updateMediaPath(virtualFolderName, pathInfo) {\n if (!virtualFolderName) {\n throw new Error('null virtualFolderName');\n }\n\n if (!pathInfo) {\n throw new Error('null pathInfo');\n }\n\n let url = 'Library/VirtualFolders/Paths/Update';\n\n url = this.getUrl(url);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n Name: virtualFolderName,\n PathInfo: pathInfo\n }),\n contentType: 'application/json'\n });\n }\n\n /**\n * Removes a media path from a virtual folder\n * @param {String} name\n */\n removeMediaPath(virtualFolderName, mediaPath, refreshLibrary) {\n if (!virtualFolderName) {\n throw new Error('null virtualFolderName');\n }\n\n if (!mediaPath) {\n throw new Error('null mediaPath');\n }\n\n let url = 'Library/VirtualFolders/Paths';\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false,\n path: mediaPath,\n name: virtualFolderName\n });\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Deletes a user\n * @param {String} id\n */\n deleteUser(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`Users/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Deletes a user image\n * @param {String} userId\n * @param {String} imageType The type of image to delete, based on the server-side ImageType enum.\n */\n deleteUserImage(userId, imageType, imageIndex) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n let url = this.getUrl(`Users/${userId}/Images/${imageType}`);\n\n if (imageIndex != null) {\n url += `/${imageIndex}`;\n }\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n deleteItemImage(itemId, imageType, imageIndex) {\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n let url = this.getUrl(`Items/${itemId}/Images`);\n\n url += `/${imageType}`;\n\n if (imageIndex != null) {\n url += `/${imageIndex}`;\n }\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n deleteItem(itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Items/${itemId}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n stopActiveEncodings(playSessionId) {\n const options = {\n deviceId: this.deviceId()\n };\n\n if (playSessionId) {\n options.PlaySessionId = playSessionId;\n }\n\n const url = this.getUrl('Videos/ActiveEncodings', options);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n reportCapabilities(options) {\n const url = this.getUrl('Sessions/Capabilities/Full');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(options),\n contentType: 'application/json'\n });\n }\n\n updateItemImageIndex(itemId, imageType, imageIndex, newIndex) {\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n const options = { newIndex };\n\n const url = this.getUrl(`Items/${itemId}/Images/${imageType}/${imageIndex}/Index`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getItemImageInfos(itemId) {\n const url = this.getUrl(`Items/${itemId}/Images`);\n\n return this.getJSON(url);\n }\n\n getCriticReviews(itemId, options) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Items/${itemId}/CriticReviews`, options);\n\n return this.getJSON(url);\n }\n\n getItemDownloadUrl(itemId) {\n if (!itemId) {\n throw new Error('itemId cannot be empty');\n }\n\n const url = `Items/${itemId}/Download`;\n\n return this.getUrl(url, {\n api_key: this.accessToken()\n });\n }\n\n getSessions(options) {\n const url = this.getUrl('Sessions', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Uploads a user image\n * @param {String} userId\n * @param {String} imageType The type of image to delete, based on the server-side ImageType enum.\n * @param {Object} file The file from the input element\n */\n uploadUserImage(userId, imageType, file) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n if (!file) {\n throw new Error('File must be an image.');\n }\n\n if (!file.type.startsWith('image/')) {\n throw new Error('File must be an image.');\n }\n\n const instance = this;\n\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n\n reader.onerror = () => {\n reject();\n };\n\n reader.onabort = () => {\n reject();\n };\n\n // Closure to capture the file information.\n reader.onload = (e) => {\n // Split by a comma to remove the url: prefix\n const data = e.target.result.split(',')[1];\n\n const url = instance.getUrl(`Users/${userId}/Images/${imageType}`);\n\n instance\n .ajax({\n type: 'POST',\n url,\n data,\n contentType: `image/${file.name.substring(file.name.lastIndexOf('.') + 1)}`\n })\n .then(resolve, reject);\n };\n\n // Read in the image file as a data URL.\n reader.readAsDataURL(file);\n });\n }\n\n uploadItemImage(itemId, imageType, file) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n if (!file) {\n throw new Error('File must be an image.');\n }\n\n if (!file.type.startsWith('image/')) {\n throw new Error('File must be an image.');\n }\n\n let url = this.getUrl(`Items/${itemId}/Images`);\n\n url += `/${imageType}`;\n const instance = this;\n\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n\n reader.onerror = () => {\n reject();\n };\n\n reader.onabort = () => {\n reject();\n };\n\n // Closure to capture the file information.\n reader.onload = (e) => {\n // Split by a comma to remove the url: prefix\n const data = e.target.result.split(',')[1];\n\n instance\n .ajax({\n type: 'POST',\n url,\n data,\n contentType: `image/${file.name.substring(file.name.lastIndexOf('.') + 1)}`\n })\n .then(resolve, reject);\n };\n\n // Read in the image file as a data URL.\n reader.readAsDataURL(file);\n });\n }\n\n /**\n * Gets the list of installed plugins on the server\n */\n getInstalledPlugins() {\n const options = {};\n\n const url = this.getUrl('Plugins', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a user by id\n * @param {String} id\n */\n getUser(id) {\n if (!id) {\n throw new Error('Must supply a userId');\n }\n\n const url = this.getUrl(`Users/${id}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a studio\n */\n getStudio(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Studios/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a genre\n */\n getGenre(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Genres/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n getMusicGenre(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`MusicGenres/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets an artist\n */\n getArtist(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Artists/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a Person\n */\n getPerson(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Persons/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n getPublicUsers() {\n const url = this.getUrl('users/public');\n\n return this.ajax(\n {\n type: 'GET',\n url,\n dataType: 'json'\n },\n false\n );\n }\n\n /**\n * Gets all users from the server\n */\n getUsers(options) {\n const url = this.getUrl('users', options || {});\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all available parental ratings from the server\n */\n getParentalRatings() {\n const url = this.getUrl('Localization/ParentalRatings');\n\n return this.getJSON(url);\n }\n\n getDefaultImageQuality(imageType) {\n return imageType.toLowerCase() === 'backdrop' ? 80 : 90;\n }\n\n /**\n * Constructs a url for a user image\n * @param {String} userId\n * @param {Object} options\n * Options supports the following properties:\n * width - download the image at a fixed width\n * height - download the image at a fixed height\n * maxWidth - download the image at a maxWidth\n * maxHeight - download the image at a maxHeight\n * quality - A scale of 0-100. This should almost always be omitted as the default will suffice.\n * For best results do not specify both width and height together, as aspect ratio might be altered.\n */\n getUserImageUrl(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n\n let url = `Users/${userId}/Images/${options.type}`;\n\n if (options.index != null) {\n url += `/${options.index}`;\n }\n\n normalizeImageOptions(this, options);\n\n // Don't put these on the query string\n delete options.type;\n delete options.index;\n\n return this.getUrl(url, options);\n }\n\n /**\n * Constructs a url for an item image\n * @param {String} itemId\n * @param {Object} options\n * Options supports the following properties:\n * type - Primary, logo, backdrop, etc. See the server-side enum ImageType\n * index - When downloading a backdrop, use this to specify which one (omitting is equivalent to zero)\n * width - download the image at a fixed width\n * height - download the image at a fixed height\n * maxWidth - download the image at a maxWidth\n * maxHeight - download the image at a maxHeight\n * quality - A scale of 0-100. This should almost always be omitted as the default will suffice.\n * For best results do not specify both width and height together, as aspect ratio might be altered.\n */\n getImageUrl(itemId, options) {\n if (!itemId) {\n throw new Error('itemId cannot be empty');\n }\n\n options = options || {};\n\n let url = `Items/${itemId}/Images/${options.type}`;\n\n if (options.index != null) {\n url += `/${options.index}`;\n }\n\n options.quality = options.quality || this.getDefaultImageQuality(options.type);\n\n if (this.normalizeImageOptions) {\n this.normalizeImageOptions(options);\n }\n\n // Don't put these on the query string\n delete options.type;\n delete options.index;\n\n return this.getUrl(url, options);\n }\n\n getScaledImageUrl(itemId, options) {\n if (!itemId) {\n throw new Error('itemId cannot be empty');\n }\n\n options = options || {};\n\n let url = `Items/${itemId}/Images/${options.type}`;\n\n if (options.index != null) {\n url += `/${options.index}`;\n }\n\n normalizeImageOptions(this, options);\n\n // Don't put these on the query string\n delete options.type;\n delete options.index;\n delete options.minScale;\n\n return this.getUrl(url, options);\n }\n\n getThumbImageUrl(item, options) {\n if (!item) {\n throw new Error('null item');\n }\n\n options = options || {};\n\n options.imageType = 'thumb';\n\n if (item.ImageTags && item.ImageTags.Thumb) {\n options.tag = item.ImageTags.Thumb;\n return this.getImageUrl(item.Id, options);\n } else if (item.ParentThumbItemId) {\n options.tag = item.ImageTags.ParentThumbImageTag;\n return this.getImageUrl(item.ParentThumbItemId, options);\n } else {\n return null;\n }\n }\n\n /**\n * Updates a user's password\n * @param {String} userId\n * @param {String} currentPassword\n * @param {String} newPassword\n */\n updateUserPassword(userId, currentPassword, newPassword) {\n if (!userId) {\n return Promise.reject();\n }\n\n const url = this.getUrl(`Users/${userId}/Password`);\n\n return this.ajax({\n type: 'POST',\n url: url,\n data: JSON.stringify({\n CurrentPw: currentPassword || '',\n NewPw: newPassword\n }),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates a user's easy password\n * @param {String} userId\n * @param {String} newPassword\n */\n updateEasyPassword(userId, newPassword) {\n if (!userId) {\n Promise.reject();\n return;\n }\n\n const url = this.getUrl(`Users/${userId}/EasyPassword`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: {\n NewPw: newPassword\n }\n });\n }\n\n /**\n * Resets a user's password\n * @param {String} userId\n */\n resetUserPassword(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Users/${userId}/Password`);\n\n const postData = {};\n\n postData.resetPassword = true;\n\n return this.ajax({\n type: 'POST',\n url,\n data: postData\n });\n }\n\n resetEasyPassword(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Users/${userId}/EasyPassword`);\n\n const postData = {};\n\n postData.resetPassword = true;\n\n return this.ajax({\n type: 'POST',\n url,\n data: postData\n });\n }\n\n /**\n * Updates the server's configuration\n * @param {Object} configuration\n */\n updateServerConfiguration(configuration) {\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl('System/Configuration');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n updateNamedConfiguration(name, configuration) {\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl(`System/Configuration/${name}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n updateItem(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl(`Items/${item.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates plugin security info\n */\n updatePluginSecurityInfo(info) {\n const url = this.getUrl('Plugins/SecurityInfo');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(info),\n contentType: 'application/json'\n });\n }\n\n /**\n * Creates a user\n * @param {Object} user\n */\n createUser(user) {\n const url = this.getUrl('Users/New');\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(user),\n contentType: 'application/json',\n headers: {\n accept: 'application/json'\n }\n });\n }\n\n /**\n * Updates a user\n * @param {Object} user\n */\n updateUser(user) {\n if (!user) {\n throw new Error('null user');\n }\n\n const url = this.getUrl(`Users/${user.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(user),\n contentType: 'application/json'\n });\n }\n\n updateUserPolicy(userId, policy) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!policy) {\n throw new Error('null policy');\n }\n\n const url = this.getUrl(`Users/${userId}/Policy`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(policy),\n contentType: 'application/json'\n });\n }\n\n updateUserConfiguration(userId, configuration) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl(`Users/${userId}/Configuration`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates the Triggers for a ScheduledTask\n * @param {String} id\n * @param {Object} triggers\n */\n updateScheduledTaskTriggers(id, triggers) {\n if (!id) {\n throw new Error('null id');\n }\n\n if (!triggers) {\n throw new Error('null triggers');\n }\n\n const url = this.getUrl(`ScheduledTasks/${id}/Triggers`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(triggers),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates a plugin's configuration\n * @param {String} Id\n * @param {Object} configuration\n */\n updatePluginConfiguration(id, configuration) {\n if (!id) {\n throw new Error('null Id');\n }\n\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl(`Plugins/${id}/Configuration`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n getAncestorItems(itemId, userId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Items/${itemId}/Ancestors`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets items based on a query, typically for children of a folder\n * @param {String} userId\n * @param {Object} options\n * Options accepts the following properties:\n * itemId - Localize the search to a specific folder (root if omitted)\n * startIndex - Use for paging\n * limit - Use to limit results to a certain number of items\n * filter - Specify one or more ItemFilters, comma delimeted (see server-side enum)\n * sortBy - Specify an ItemSortBy (comma-delimeted list see server-side enum)\n * sortOrder - ascending/descending\n * fields - additional fields to include aside from basic info. This is a comma delimited list. See server-side enum ItemFields.\n * index - the name of the dynamic, localized index function\n * dynamicSortBy - the name of the dynamic localized sort function\n * recursive - Whether or not the query should be recursive\n * searchTerm - search term to use as a filter\n */\n getItems(userId, options) {\n let url;\n\n if ((typeof userId).toString().toLowerCase() === 'string') {\n url = this.getUrl(`Users/${userId}/Items`, options);\n } else {\n url = this.getUrl('Items', options);\n }\n\n return this.getJSON(url);\n }\n\n getResumableItems(userId, options) {\n if (this.isMinServerVersion('3.2.33')) {\n return this.getJSON(this.getUrl(`Users/${userId}/Items/Resume`, options));\n }\n\n return this.getItems(\n userId,\n Object.assign(\n {\n SortBy: 'DatePlayed',\n SortOrder: 'Descending',\n Filters: 'IsResumable',\n Recursive: true,\n CollapseBoxSetItems: false,\n ExcludeLocationTypes: 'Virtual'\n },\n options\n )\n );\n }\n\n getMovieRecommendations(options) {\n return this.getJSON(this.getUrl('Movies/Recommendations', options));\n }\n\n getUpcomingEpisodes(options) {\n return this.getJSON(this.getUrl('Shows/Upcoming', options));\n }\n\n getUserViews(options = {}, userId) {\n const url = this.getUrl(`Users/${userId || this.getCurrentUserId()}/Views`, options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets artists from an item\n */\n getArtists(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Artists', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets artists from an item\n */\n getAlbumArtists(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Artists/AlbumArtists', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets genres from an item\n */\n getGenres(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Genres', options);\n\n return this.getJSON(url);\n }\n\n getMusicGenres(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('MusicGenres', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets people from an item\n */\n getPeople(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Persons', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets studios from an item\n */\n getStudios(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Studios', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets local trailers for an item\n */\n getLocalTrailers(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/LocalTrailers`);\n\n return this.getJSON(url);\n }\n\n getAdditionalVideoParts(userId, itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Videos/${itemId}/AdditionalParts`, options);\n\n return this.getJSON(url);\n }\n\n getThemeMedia(userId, itemId, inherit) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n options.InheritFromParent = inherit || false;\n\n const url = this.getUrl(`Items/${itemId}/ThemeMedia`, options);\n\n return this.getJSON(url);\n }\n\n getSearchHints(options) {\n const url = this.getUrl('Search/Hints', options);\n const serverId = this.serverId();\n\n return this.getJSON(url).then((result) => {\n result.SearchHints.forEach((i) => {\n i.ServerId = serverId;\n });\n return result;\n });\n }\n\n /**\n * Gets special features for an item\n */\n getSpecialFeatures(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/SpecialFeatures`);\n\n return this.getJSON(url);\n }\n\n getDateParamValue(date) {\n function formatDigit(i) {\n return i < 10 ? `0${i}` : i;\n }\n\n const d = date;\n\n return `${d.getFullYear()}${formatDigit(d.getMonth() + 1)}${formatDigit(d.getDate())}${formatDigit(\n d.getHours()\n )}${formatDigit(d.getMinutes())}${formatDigit(d.getSeconds())}`;\n }\n\n markPlayed(userId, itemId, date) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (date) {\n options.DatePlayed = this.getDateParamValue(date);\n }\n\n const url = this.getUrl(`Users/${userId}/PlayedItems/${itemId}`, options);\n\n return this.ajax({\n type: 'POST',\n url,\n dataType: 'json'\n });\n }\n\n markUnplayed(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/PlayedItems/${itemId}`);\n\n return this.ajax({\n type: 'DELETE',\n url,\n dataType: 'json'\n });\n }\n\n /**\n * Updates a user's favorite status for an item.\n * @param {String} userId\n * @param {String} itemId\n * @param {Boolean} isFavorite\n */\n updateFavoriteStatus(userId, itemId, isFavorite) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/FavoriteItems/${itemId}`);\n\n const method = isFavorite ? 'POST' : 'DELETE';\n\n return this.ajax({\n type: method,\n url,\n dataType: 'json'\n });\n }\n\n /**\n * Updates a user's personal rating for an item\n * @param {String} userId\n * @param {String} itemId\n * @param {Boolean} likes\n */\n updateUserItemRating(userId, itemId, likes) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/Rating`, {\n likes\n });\n\n return this.ajax({\n type: 'POST',\n url,\n dataType: 'json'\n });\n }\n\n getItemCounts(userId) {\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl('Items/Counts', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Clears a user's personal rating for an item\n * @param {String} userId\n * @param {String} itemId\n */\n clearUserItemRating(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/Rating`);\n\n return this.ajax({\n type: 'DELETE',\n url,\n dataType: 'json'\n });\n }\n\n /**\n * Reports the user has started playing something\n * @param {String} userId\n * @param {String} itemId\n */\n reportPlaybackStart(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n this.lastPlaybackProgressReport = 0;\n this.lastPlaybackProgressReportTicks = null;\n stopBitrateDetection(this);\n\n cancelReportPlaybackProgressPromise(this);\n const url = this.getUrl('Sessions/Playing');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(options),\n contentType: 'application/json',\n url\n });\n }\n\n /**\n * Reports progress viewing an item\n * @param {String} userId\n * @param {String} itemId\n */\n reportPlaybackProgress(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const eventName = options.EventName || 'timeupdate';\n let reportRateLimitTime = reportRateLimits[eventName] || 0;\n\n const now = new Date().getTime();\n const msSinceLastReport = now - (this.lastPlaybackProgressReport || 0);\n const newPositionTicks = options.PositionTicks;\n\n if (msSinceLastReport < reportRateLimitTime && eventName === 'timeupdate' && newPositionTicks) {\n const expectedReportTicks = 1e4 * msSinceLastReport + (this.lastPlaybackProgressReportTicks || 0);\n if (Math.abs(newPositionTicks - expectedReportTicks) >= 5e7) reportRateLimitTime = 0;\n }\n\n if (\n reportRateLimitTime <\n (this.reportPlaybackProgressTimeout !== undefined ? this.reportPlaybackProgressTimeout : 1e6)\n ) {\n cancelReportPlaybackProgressPromise(this);\n }\n\n this.lastPlaybackProgressOptions = options;\n\n /* eslint-disable-next-line @typescript-eslint/no-misused-promises */\n if (this.reportPlaybackProgressPromise) return Promise.resolve();\n\n let instance = this;\n let promise;\n let cancelled = false;\n\n let resetPromise = function () {\n if (instance.reportPlaybackProgressPromise !== promise) return;\n\n delete instance.lastPlaybackProgressOptions;\n delete instance.reportPlaybackProgressTimeout;\n delete instance.reportPlaybackProgressPromise;\n delete instance.reportPlaybackProgressCancel;\n };\n\n let sendReport = function (lastOptions) {\n resetPromise();\n\n if (!lastOptions) throw new Error('null options');\n\n instance.lastPlaybackProgressReport = new Date().getTime();\n instance.lastPlaybackProgressReportTicks = lastOptions.PositionTicks;\n\n const url = instance.getUrl('Sessions/Playing/Progress');\n return instance.ajax({\n type: 'POST',\n data: JSON.stringify(lastOptions),\n contentType: 'application/json',\n url: url\n });\n };\n\n let delay = Math.max(0, reportRateLimitTime - msSinceLastReport);\n\n promise = new Promise((resolve, reject) => setTimeout(resolve, delay))\n .then(() => {\n if (cancelled) return Promise.resolve();\n return sendReport(instance.lastPlaybackProgressOptions);\n })\n .finally(() => {\n resetPromise();\n });\n\n this.reportPlaybackProgressTimeout = reportRateLimitTime;\n this.reportPlaybackProgressPromise = promise;\n this.reportPlaybackProgressCancel = function () {\n cancelled = true;\n resetPromise();\n };\n\n return promise;\n }\n\n reportOfflineActions(actions) {\n if (!actions) {\n throw new Error('null actions');\n }\n\n const url = this.getUrl('Sync/OfflineActions');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(actions),\n contentType: 'application/json',\n url\n });\n }\n\n syncData(data) {\n if (!data) {\n throw new Error('null data');\n }\n\n const url = this.getUrl('Sync/Data');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(data),\n contentType: 'application/json',\n url,\n dataType: 'json'\n });\n }\n\n getReadySyncItems(deviceId) {\n if (!deviceId) {\n throw new Error('null deviceId');\n }\n\n const url = this.getUrl('Sync/Items/Ready', {\n TargetId: deviceId\n });\n\n return this.getJSON(url);\n }\n\n reportSyncJobItemTransferred(syncJobItemId) {\n if (!syncJobItemId) {\n throw new Error('null syncJobItemId');\n }\n\n const url = this.getUrl(`Sync/JobItems/${syncJobItemId}/Transferred`);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n cancelSyncItems(itemIds, targetId) {\n if (!itemIds) {\n throw new Error('null itemIds');\n }\n\n const url = this.getUrl(`Sync/${targetId || this.deviceId()}/Items`, {\n ItemIds: itemIds.join(',')\n });\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Reports a user has stopped playing an item\n * @param {String} userId\n * @param {String} itemId\n */\n reportPlaybackStopped(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n this.lastPlaybackProgressReport = 0;\n this.lastPlaybackProgressReportTicks = null;\n redetectBitrate(this);\n\n cancelReportPlaybackProgressPromise(this);\n const url = this.getUrl('Sessions/Playing/Stopped');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(options),\n contentType: 'application/json',\n url\n });\n }\n\n sendPlayCommand(sessionId, options) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!options) {\n throw new Error('null options');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Playing`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n sendCommand(sessionId, command) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!command) {\n throw new Error('null command');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Command`);\n\n const ajaxOptions = {\n type: 'POST',\n url\n };\n\n ajaxOptions.data = JSON.stringify(command);\n ajaxOptions.contentType = 'application/json';\n\n return this.ajax(ajaxOptions);\n }\n\n sendMessageCommand(sessionId, options) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!options) {\n throw new Error('null options');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Message`);\n\n const ajaxOptions = {\n type: 'POST',\n url\n };\n\n ajaxOptions.data = JSON.stringify(options);\n ajaxOptions.contentType = 'application/json';\n\n return this.ajax(ajaxOptions);\n }\n\n sendPlayStateCommand(sessionId, command, options) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!command) {\n throw new Error('null command');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Playing/${command}`, options || {});\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Gets a list of all the active SyncPlay groups from the server.\n * @returns {Promise} A Promise that resolves to the list of active groups.\n * @since 10.6.0\n */\n getSyncPlayGroups() {\n const url = this.getUrl(`SyncPlay/List`);\n\n return this.ajax({\n type: 'GET',\n url: url\n });\n }\n\n /**\n * Creates a SyncPlay group on the server with the current client as member.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n createSyncPlayGroup() {\n const url = this.getUrl(`SyncPlay/New`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Joins the client to a given SyncPlay group on the server.\n * @param {object} options Information about the SyncPlay group to join.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n joinSyncPlayGroup(options = {}) {\n const url = this.getUrl(`SyncPlay/Join`, options);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Leaves the current SyncPlay group.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n leaveSyncPlayGroup() {\n const url = this.getUrl(`SyncPlay/Leave`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Sends a ping to the SyncPlay group on the server.\n * @param {object} options Information about the ping\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n sendSyncPlayPing(options = {}) {\n const url = this.getUrl(`SyncPlay/Ping`, options);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Requests a playback start for the SyncPlay group\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n requestSyncPlayStart() {\n const url = this.getUrl(`SyncPlay/Play`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Requests a playback pause for the SyncPlay group\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n requestSyncPlayPause() {\n const url = this.getUrl(`SyncPlay/Pause`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Requests a playback seek for the SyncPlay group\n * @param {object} options Object containing the requested seek position.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n requestSyncPlaySeek(options = {}) {\n const url = this.getUrl(`SyncPlay/Seek`, options);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n createPackageReview(review) {\n const url = this.getUrl(`Packages/Reviews/${review.id}`, review);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getPackageReviews(packageId, minRating, maxRating, limit) {\n if (!packageId) {\n throw new Error('null packageId');\n }\n\n const options = {};\n\n if (minRating) {\n options.MinRating = minRating;\n }\n if (maxRating) {\n options.MaxRating = maxRating;\n }\n if (limit) {\n options.Limit = limit;\n }\n\n const url = this.getUrl(`Packages/${packageId}/Reviews`, options);\n\n return this.getJSON(url);\n }\n\n getSavedEndpointInfo() {\n return this._endPointInfo;\n }\n\n getEndpointInfo() {\n const savedValue = this._endPointInfo;\n if (savedValue) {\n return Promise.resolve(savedValue);\n }\n\n const instance = this;\n return this.getJSON(this.getUrl('System/Endpoint')).then((endPointInfo) => {\n setSavedEndpointInfo(instance, endPointInfo);\n return endPointInfo;\n });\n }\n\n getLatestItems(options = {}) {\n return this.getJSON(this.getUrl(`Users/${this.getCurrentUserId()}/Items/Latest`, options));\n }\n\n getFilters(options) {\n return this.getJSON(this.getUrl('Items/Filters2', options));\n }\n\n setSystemInfo(info) {\n this._serverVersion = info.Version;\n }\n\n serverVersion() {\n return this._serverVersion;\n }\n\n isMinServerVersion(version) {\n const serverVersion = this.serverVersion();\n\n if (serverVersion) {\n return compareVersions(serverVersion, version) >= 0;\n }\n\n return false;\n }\n\n handleMessageReceived(msg) {\n onMessageReceivedInternal(this, msg);\n }\n}\n\nfunction setSavedEndpointInfo(instance, info) {\n instance._endPointInfo = info;\n}\n\nfunction getTryConnectPromise(instance, url, state, resolve, reject) {\n console.log('getTryConnectPromise ' + url);\n\n fetchWithTimeout(\n instance.getUrl('system/info/public', null, url),\n {\n method: 'GET',\n accept: 'application/json'\n\n // Commenting this out since the fetch api doesn't have a timeout option yet\n //timeout: timeout\n },\n 15000\n ).then(\n () => {\n if (!state.resolved) {\n state.resolved = true;\n\n console.log('Reconnect succeeded to ' + url);\n instance.serverAddress(url);\n resolve();\n }\n },\n () => {\n if (!state.resolved) {\n console.log('Reconnect failed to ' + url);\n\n state.rejects++;\n if (state.rejects >= state.numAddresses) {\n reject();\n }\n }\n }\n );\n}\n\nfunction tryReconnectInternal(instance) {\n const addresses = [];\n const addressesStrings = [];\n\n const serverInfo = instance.serverInfo();\n if (serverInfo.LocalAddress && addressesStrings.indexOf(serverInfo.LocalAddress) === -1) {\n addresses.push({ url: serverInfo.LocalAddress, timeout: 0 });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (serverInfo.ManualAddress && addressesStrings.indexOf(serverInfo.ManualAddress) === -1) {\n addresses.push({ url: serverInfo.ManualAddress, timeout: 100 });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (serverInfo.RemoteAddress && addressesStrings.indexOf(serverInfo.RemoteAddress) === -1) {\n addresses.push({ url: serverInfo.RemoteAddress, timeout: 200 });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n\n console.log('tryReconnect: ' + addressesStrings.join('|'));\n\n return new Promise((resolve, reject) => {\n const state = {};\n state.numAddresses = addresses.length;\n state.rejects = 0;\n\n addresses.map((url) => {\n setTimeout(() => {\n if (!state.resolved) {\n getTryConnectPromise(instance, url.url, state, resolve, reject);\n }\n }, url.timeout);\n });\n });\n}\n\nfunction tryReconnect(instance, retryCount) {\n retryCount = retryCount || 0;\n\n if (retryCount >= 20) {\n return Promise.reject();\n }\n\n return tryReconnectInternal(instance).catch((err) => {\n console.log('error in tryReconnectInternal: ' + (err || ''));\n\n return new Promise((resolve, reject) => {\n setTimeout(() => {\n tryReconnect(instance, retryCount + 1).then(resolve, reject);\n }, 500);\n });\n });\n}\n\nfunction getCachedUser(instance, userId) {\n const serverId = instance.serverId();\n if (!serverId) {\n return null;\n }\n\n const json = appStorage.getItem(`user-${userId}-${serverId}`);\n\n if (json) {\n return JSON.parse(json);\n }\n\n return null;\n}\n\nfunction onWebSocketMessage(msg) {\n const instance = this;\n msg = JSON.parse(msg.data);\n onMessageReceivedInternal(instance, msg);\n}\n\nconst messageIdsReceived = {};\n\nfunction onMessageReceivedInternal(instance, msg) {\n const messageId = msg.MessageId;\n if (messageId) {\n // message was already received via another protocol\n if (messageIdsReceived[messageId]) {\n return;\n }\n\n messageIdsReceived[messageId] = true;\n }\n\n if (msg.MessageType === 'UserDeleted') {\n instance._currentUser = null;\n } else if (msg.MessageType === 'UserUpdated' || msg.MessageType === 'UserConfigurationUpdated') {\n const user = msg.Data;\n if (user.Id === instance.getCurrentUserId()) {\n instance._currentUser = null;\n }\n } else if (msg.MessageType === 'KeepAlive') {\n console.debug('Received KeepAlive from server.');\n } else if (msg.MessageType === 'ForceKeepAlive') {\n console.debug(`Received ForceKeepAlive from server. Timeout is ${msg.Data} seconds.`);\n instance.sendWebSocketMessage('KeepAlive');\n scheduleKeepAlive(instance, msg.Data);\n }\n\n events.trigger(instance, 'message', [msg]);\n}\n\n/**\n * Starts a poller that sends KeepAlive messages using a WebSocket connection.\n * @param {Object} instance The WebSocket connection.\n * @param {number} timeout The number of seconds after which the WebSocket is considered lost by the server.\n * @returns {number} The id of the interval.\n * @since 10.6.0\n */\nfunction scheduleKeepAlive(instance, timeout) {\n clearKeepAlive(instance);\n instance.keepAliveInterval = setInterval(() => {\n instance.sendWebSocketMessage('KeepAlive');\n }, timeout * 1000 * 0.5);\n return instance.keepAliveInterval;\n}\n\n/**\n * Stops the poller that is sending KeepAlive messages on a WebSocket connection.\n * @param {Object} instance The WebSocket connection.\n * @since 10.6.0\n */\nfunction clearKeepAlive(instance) {\n console.debug('Clearing KeepAlive for', instance);\n if (instance.keepAliveInterval) {\n clearInterval(instance.keepAliveInterval);\n instance.keepAliveInterval = null;\n }\n}\n\nfunction onWebSocketOpen() {\n const instance = this;\n console.log('web socket connection opened');\n events.trigger(instance, 'websocketopen');\n}\n\nfunction onWebSocketError() {\n const instance = this;\n clearKeepAlive(instance);\n events.trigger(instance, 'websocketerror');\n}\n\nfunction setSocketOnClose(apiClient, socket) {\n socket.onclose = () => {\n console.log('web socket closed');\n\n clearKeepAlive(socket);\n if (apiClient._webSocket === socket) {\n console.log('nulling out web socket');\n apiClient._webSocket = null;\n }\n\n setTimeout(() => {\n events.trigger(apiClient, 'websocketclose');\n }, 0);\n };\n}\n\nfunction normalizeReturnBitrate(instance, bitrate) {\n if (!bitrate) {\n if (instance.lastDetectedBitrate) {\n return instance.lastDetectedBitrate;\n }\n\n return Promise.reject();\n }\n\n let result = Math.round(bitrate * 0.7);\n\n // allow configuration of this\n if (instance.getMaxBandwidth) {\n const maxRate = instance.getMaxBandwidth();\n if (maxRate) {\n result = Math.min(result, maxRate);\n }\n }\n\n instance.lastDetectedBitrate = result;\n instance.lastDetectedBitrateTime = new Date().getTime();\n\n return result;\n}\n\nfunction detectBitrateInternal(instance, tests, index, currentBitrate) {\n if (index >= tests.length) {\n return normalizeReturnBitrate(instance, currentBitrate);\n }\n\n const test = tests[index];\n\n return instance.getDownloadSpeed(test.bytes).then(\n (bitrate) => {\n if (bitrate < test.threshold) {\n return normalizeReturnBitrate(instance, bitrate);\n } else {\n return detectBitrateInternal(instance, tests, index + 1, bitrate);\n }\n },\n () => normalizeReturnBitrate(instance, currentBitrate)\n );\n}\n\nfunction detectBitrateWithEndpointInfo(instance, endpointInfo) {\n if (endpointInfo.IsInNetwork) {\n const result = 140000000;\n instance.lastDetectedBitrate = result;\n instance.lastDetectedBitrateTime = new Date().getTime();\n return result;\n }\n\n return detectBitrateInternal(\n instance,\n [\n {\n bytes: 500000,\n threshold: 500000\n },\n {\n bytes: 1000000,\n threshold: 20000000\n },\n {\n bytes: 3000000,\n threshold: 50000000\n }\n ],\n 0\n );\n}\n\nfunction getRemoteImagePrefix(instance, options) {\n let urlPrefix;\n\n if (options.artist) {\n urlPrefix = `Artists/${instance.encodeName(options.artist)}`;\n delete options.artist;\n } else if (options.person) {\n urlPrefix = `Persons/${instance.encodeName(options.person)}`;\n delete options.person;\n } else if (options.genre) {\n urlPrefix = `Genres/${instance.encodeName(options.genre)}`;\n delete options.genre;\n } else if (options.musicGenre) {\n urlPrefix = `MusicGenres/${instance.encodeName(options.musicGenre)}`;\n delete options.musicGenre;\n } else if (options.studio) {\n urlPrefix = `Studios/${instance.encodeName(options.studio)}`;\n delete options.studio;\n } else {\n urlPrefix = `Items/${options.itemId}`;\n delete options.itemId;\n }\n\n return urlPrefix;\n}\n\nfunction normalizeImageOptions(instance, options) {\n let ratio = window.devicePixelRatio || 1;\n\n if (ratio) {\n if (options.minScale) {\n ratio = Math.max(options.minScale, ratio);\n }\n\n if (options.width) {\n options.width = Math.round(options.width * ratio);\n }\n if (options.height) {\n options.height = Math.round(options.height * ratio);\n }\n if (options.maxWidth) {\n options.maxWidth = Math.round(options.maxWidth * ratio);\n }\n if (options.maxHeight) {\n options.maxHeight = Math.round(options.maxHeight * ratio);\n }\n }\n\n options.quality = options.quality || instance.getDefaultImageQuality(options.type);\n\n if (instance.normalizeImageOptions) {\n instance.normalizeImageOptions(options);\n }\n}\n\nfunction compareVersions(a, b) {\n // -1 a is smaller\n // 1 a is larger\n // 0 equal\n a = a.split('.');\n b = b.split('.');\n\n for (let i = 0, length = Math.max(a.length, b.length); i < length; i++) {\n const aVal = parseInt(a[i] || '0');\n const bVal = parseInt(b[i] || '0');\n\n if (aVal < bVal) {\n return -1;\n }\n\n if (aVal > bVal) {\n return 1;\n }\n }\n\n return 0;\n}\n\nexport default ApiClient;\n","import ApiClient from './apiClient';\n\nconst localPrefix = 'local:';\nconst localViewPrefix = 'localview:';\n\nfunction isLocalId(str) {\n return startsWith(str, localPrefix);\n}\n\nfunction isLocalViewId(str) {\n return startsWith(str, localViewPrefix);\n}\n\nfunction isTopLevelLocalViewId(str) {\n return str === 'localview';\n}\n\nfunction stripLocalPrefix(str) {\n let res = stripStart(str, localPrefix);\n res = stripStart(res, localViewPrefix);\n\n return res;\n}\n\nfunction startsWith(str, find) {\n if (str && find && str.length > find.length) {\n if (str.indexOf(find) === 0) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction stripStart(str, find) {\n if (startsWith(str, find)) {\n return str.substr(find.length);\n }\n\n return str;\n}\n\nfunction createEmptyList() {\n const result = {\n Items: [],\n TotalRecordCount: 0\n };\n\n return result;\n}\n\nfunction convertGuidToLocal(guid) {\n if (!guid) {\n return null;\n }\n\n if (isLocalId(guid)) {\n return guid;\n }\n\n return `local:${guid}`;\n}\n\nfunction adjustGuidProperties(downloadedItem) {\n downloadedItem.Id = convertGuidToLocal(downloadedItem.Id);\n downloadedItem.SeriesId = convertGuidToLocal(downloadedItem.SeriesId);\n downloadedItem.SeasonId = convertGuidToLocal(downloadedItem.SeasonId);\n\n downloadedItem.AlbumId = convertGuidToLocal(downloadedItem.AlbumId);\n downloadedItem.ParentId = convertGuidToLocal(downloadedItem.ParentId);\n downloadedItem.ParentThumbItemId = convertGuidToLocal(downloadedItem.ParentThumbItemId);\n downloadedItem.ParentPrimaryImageItemId = convertGuidToLocal(downloadedItem.ParentPrimaryImageItemId);\n downloadedItem.PrimaryImageItemId = convertGuidToLocal(downloadedItem.PrimaryImageItemId);\n downloadedItem.ParentLogoItemId = convertGuidToLocal(downloadedItem.ParentLogoItemId);\n downloadedItem.ParentBackdropItemId = convertGuidToLocal(downloadedItem.ParentBackdropItemId);\n\n downloadedItem.ParentBackdropImageTags = null;\n}\n\nfunction getLocalView(instance, serverId, userId) {\n return instance.getLocalFolders(serverId, userId).then((views) => {\n let localView = null;\n\n if (views.length > 0) {\n localView = {\n Name: instance.downloadsTitleText || 'Downloads',\n ServerId: serverId,\n Id: 'localview',\n Type: 'localview',\n IsFolder: true\n };\n }\n\n return Promise.resolve(localView);\n });\n}\n\n/**\n * Creates a new api client instance\n * @param {String} serverAddress\n * @param {String} clientName s\n * @param {String} applicationVersion\n */\nclass ApiClientCore extends ApiClient {\n constructor(\n serverAddress,\n clientName,\n applicationVersion,\n deviceName,\n deviceId,\n devicePixelRatio,\n localAssetManager\n ) {\n super(serverAddress, clientName, applicationVersion, deviceName, deviceId, devicePixelRatio);\n this.localAssetManager = localAssetManager;\n }\n\n getPlaybackInfo(itemId, options, deviceProfile) {\n const onFailure = () => ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile);\n\n if (isLocalId(itemId)) {\n return this.localAssetManager.getLocalItem(this.serverId(), stripLocalPrefix(itemId)).then((item) => {\n // TODO: This was already done during the sync process, right? If so, remove it\n const mediaSources = item.Item.MediaSources.map((m) => {\n m.SupportsDirectPlay = true;\n m.SupportsDirectStream = false;\n m.SupportsTranscoding = false;\n m.IsLocal = true;\n return m;\n });\n\n return {\n MediaSources: mediaSources\n };\n }, onFailure);\n }\n\n var instance = this;\n return this.localAssetManager.getLocalItem(this.serverId(), itemId).then((item) => {\n if (item) {\n const mediaSources = item.Item.MediaSources.map((m) => {\n m.SupportsDirectPlay = true;\n m.SupportsDirectStream = false;\n m.SupportsTranscoding = false;\n m.IsLocal = true;\n return m;\n });\n\n return instance.localAssetManager.fileExists(item.LocalPath).then((exists) => {\n if (exists) {\n const res = {\n MediaSources: mediaSources\n };\n\n return Promise.resolve(res);\n }\n\n return ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile);\n }, onFailure);\n }\n\n return ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile);\n }, onFailure);\n }\n\n getItems(userId, options) {\n const serverInfo = this.serverInfo();\n let i;\n\n if (serverInfo && options.ParentId === 'localview') {\n return this.getLocalFolders(serverInfo.Id, userId).then((items) => {\n const result = {\n Items: items,\n TotalRecordCount: items.length\n };\n\n return Promise.resolve(result);\n });\n } else if (\n serverInfo &&\n options &&\n (isLocalId(options.ParentId) ||\n isLocalId(options.SeriesId) ||\n isLocalId(options.SeasonId) ||\n isLocalViewId(options.ParentId) ||\n isLocalId(options.AlbumIds))\n ) {\n return this.localAssetManager.getViewItems(serverInfo.Id, userId, options).then((items) => {\n items.forEach((item) => {\n adjustGuidProperties(item);\n });\n\n const result = {\n Items: items,\n TotalRecordCount: items.length\n };\n\n return Promise.resolve(result);\n });\n } else if (options && options.ExcludeItemIds && options.ExcludeItemIds.length) {\n const exItems = options.ExcludeItemIds.split(',');\n\n for (i = 0; i < exItems.length; i++) {\n if (isLocalId(exItems[i])) {\n return Promise.resolve(createEmptyList());\n }\n }\n } else if (options && options.Ids && options.Ids.length) {\n const ids = options.Ids.split(',');\n let hasLocal = false;\n\n for (i = 0; i < ids.length; i++) {\n if (isLocalId(ids[i])) {\n hasLocal = true;\n }\n }\n\n if (hasLocal) {\n return this.localAssetManager.getItemsFromIds(serverInfo.Id, ids).then((items) => {\n items.forEach((item) => {\n adjustGuidProperties(item);\n });\n\n const result = {\n Items: items,\n TotalRecordCount: items.length\n };\n\n return Promise.resolve(result);\n });\n }\n }\n\n return ApiClient.prototype.getItems.call(this, userId, options);\n }\n\n getUserViews(options, userId) {\n const instance = this;\n\n options = options || {};\n\n const basePromise = ApiClient.prototype.getUserViews.call(instance, options, userId);\n\n if (!options.enableLocalView) {\n return basePromise;\n }\n\n return basePromise.then((result) => {\n const serverInfo = instance.serverInfo();\n if (serverInfo) {\n return getLocalView(instance, serverInfo.Id, userId).then((localView) => {\n if (localView) {\n result.Items.push(localView);\n result.TotalRecordCount++;\n }\n\n return Promise.resolve(result);\n });\n }\n\n return Promise.resolve(result);\n });\n }\n\n getItem(userId, itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n if (itemId) {\n itemId = itemId.toString();\n }\n\n let serverInfo;\n\n if (isTopLevelLocalViewId(itemId)) {\n serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return getLocalView(this, serverInfo.Id, userId);\n }\n }\n\n if (isLocalViewId(itemId)) {\n serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.getLocalFolders(serverInfo.Id, userId).then((items) => {\n const views = items.filter((item) => item.Id === itemId);\n\n if (views.length > 0) {\n return Promise.resolve(views[0]);\n }\n\n // TODO: Test consequence of this\n return Promise.reject();\n });\n }\n }\n\n if (isLocalId(itemId)) {\n serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.localAssetManager.getLocalItem(serverInfo.Id, stripLocalPrefix(itemId)).then((item) => {\n adjustGuidProperties(item.Item);\n\n return Promise.resolve(item.Item);\n });\n }\n }\n\n return ApiClient.prototype.getItem.call(this, userId, itemId);\n }\n\n getLocalFolders(userId) {\n const serverInfo = this.serverInfo();\n userId = userId || serverInfo.UserId;\n\n return this.localAssetManager.getViews(serverInfo.Id, userId);\n }\n\n getNextUpEpisodes(options) {\n if (options.SeriesId) {\n if (isLocalId(options.SeriesId)) {\n return Promise.resolve(createEmptyList());\n }\n }\n\n return ApiClient.prototype.getNextUpEpisodes.call(this, options);\n }\n\n getSeasons(itemId, options) {\n if (isLocalId(itemId)) {\n options.SeriesId = itemId;\n options.IncludeItemTypes = 'Season';\n return this.getItems(this.getCurrentUserId(), options);\n }\n\n return ApiClient.prototype.getSeasons.call(this, itemId, options);\n }\n\n getEpisodes(itemId, options) {\n if (isLocalId(options.SeasonId) || isLocalId(options.seasonId)) {\n options.SeriesId = itemId;\n options.IncludeItemTypes = 'Episode';\n return this.getItems(this.getCurrentUserId(), options);\n }\n\n // get episodes by recursion\n if (isLocalId(itemId)) {\n options.SeriesId = itemId;\n options.IncludeItemTypes = 'Episode';\n return this.getItems(this.getCurrentUserId(), options);\n }\n\n return ApiClient.prototype.getEpisodes.call(this, itemId, options);\n }\n\n getLatestOfflineItems(options) {\n // Supported options\n // MediaType - Audio/Video/Photo/Book/Game\n // Limit\n // Filters: 'IsNotFolder' or 'IsFolder'\n\n options.SortBy = 'DateCreated';\n options.SortOrder = 'Descending';\n\n const serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.localAssetManager.getViewItems(serverInfo.Id, null, options).then((items) => {\n items.forEach((item) => {\n adjustGuidProperties(item);\n });\n\n return Promise.resolve(items);\n });\n }\n\n return Promise.resolve([]);\n }\n\n getThemeMedia(userId, itemId, inherit) {\n if (isLocalViewId(itemId) || isLocalId(itemId) || isTopLevelLocalViewId(itemId)) {\n return Promise.reject();\n }\n\n return ApiClient.prototype.getThemeMedia.call(this, userId, itemId, inherit);\n }\n\n getSpecialFeatures(userId, itemId) {\n if (isLocalId(itemId)) {\n return Promise.resolve([]);\n }\n\n return ApiClient.prototype.getSpecialFeatures.call(this, userId, itemId);\n }\n\n getSimilarItems(itemId, options) {\n if (isLocalId(itemId)) {\n return Promise.resolve(createEmptyList());\n }\n\n return ApiClient.prototype.getSimilarItems.call(this, itemId, options);\n }\n\n updateFavoriteStatus(userId, itemId, isFavorite) {\n if (isLocalId(itemId)) {\n return Promise.resolve();\n }\n\n return ApiClient.prototype.updateFavoriteStatus.call(this, userId, itemId, isFavorite);\n }\n\n getScaledImageUrl(itemId, options) {\n if (isLocalId(itemId) || (options && options.itemid && isLocalId(options.itemid))) {\n const serverInfo = this.serverInfo();\n const id = stripLocalPrefix(itemId);\n\n return this.localAssetManager.getImageUrl(serverInfo.Id, id, options);\n }\n\n return ApiClient.prototype.getScaledImageUrl.call(this, itemId, options);\n }\n\n reportPlaybackStart(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n if (isLocalId(options.ItemId)) {\n return Promise.resolve();\n }\n\n return ApiClient.prototype.reportPlaybackStart.call(this, options);\n }\n\n reportPlaybackProgress(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n if (isLocalId(options.ItemId)) {\n const serverInfo = this.serverInfo();\n\n if (serverInfo) {\n const instance = this;\n return this.localAssetManager\n .getLocalItem(serverInfo.Id, stripLocalPrefix(options.ItemId))\n .then((item) => {\n const libraryItem = item.Item;\n\n if (libraryItem.MediaType === 'Video' || libraryItem.Type === 'AudioBook') {\n libraryItem.UserData = libraryItem.UserData || {};\n libraryItem.UserData.PlaybackPositionTicks = options.PositionTicks;\n libraryItem.UserData.PlayedPercentage = Math.min(\n libraryItem.RunTimeTicks\n ? 100 * ((options.PositionTicks || 0) / libraryItem.RunTimeTicks)\n : 0,\n 100\n );\n return instance.localAssetManager.addOrUpdateLocalItem(item);\n }\n\n return Promise.resolve();\n });\n }\n\n return Promise.resolve();\n }\n\n return ApiClient.prototype.reportPlaybackProgress.call(this, options);\n }\n\n reportPlaybackStopped(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n if (isLocalId(options.ItemId)) {\n const serverInfo = this.serverInfo();\n\n const action = {\n Date: new Date().getTime(),\n ItemId: stripLocalPrefix(options.ItemId),\n PositionTicks: options.PositionTicks,\n ServerId: serverInfo.Id,\n Type: 0, // UserActionType.PlayedItem\n UserId: this.getCurrentUserId()\n };\n\n return this.localAssetManager.recordUserAction(action);\n }\n\n return ApiClient.prototype.reportPlaybackStopped.call(this, options);\n }\n\n getIntros(itemId) {\n if (isLocalId(itemId)) {\n return Promise.resolve({\n Items: [],\n TotalRecordCount: 0\n });\n }\n\n return ApiClient.prototype.getIntros.call(this, itemId);\n }\n\n getInstantMixFromItem(itemId, options) {\n if (isLocalId(itemId)) {\n return Promise.resolve({\n Items: [],\n TotalRecordCount: 0\n });\n }\n\n return ApiClient.prototype.getInstantMixFromItem.call(this, itemId, options);\n }\n\n getItemDownloadUrl(itemId) {\n if (isLocalId(itemId)) {\n const serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.localAssetManager\n .getLocalItem(serverInfo.Id, stripLocalPrefix(itemId))\n .then((item) => Promise.resolve(item.LocalPath));\n }\n }\n\n return ApiClient.prototype.getItemDownloadUrl.call(this, itemId);\n }\n}\n\nexport default ApiClientCore;\n","import events from './events';\nimport ApiClient from './apiClient';\n\nconst defaultTimeout = 20000;\n\nconst ConnectionMode = {\n Local: 0,\n Remote: 1,\n Manual: 2\n};\n\nfunction getServerAddress(server, mode) {\n switch (mode) {\n case ConnectionMode.Local:\n return server.LocalAddress;\n case ConnectionMode.Manual:\n return server.ManualAddress;\n case ConnectionMode.Remote:\n return server.RemoteAddress;\n default:\n return server.ManualAddress || server.LocalAddress || server.RemoteAddress;\n }\n}\n\nfunction paramsToString(params) {\n const values = [];\n\n for (const key in params) {\n const value = params[key];\n\n if (value !== null && value !== undefined && value !== '') {\n values.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);\n }\n }\n return values.join('&');\n}\n\nfunction resolveFailure(instance, resolve) {\n resolve({\n State: 'Unavailable'\n });\n}\n\nfunction mergeServers(credentialProvider, list1, list2) {\n for (let i = 0, length = list2.length; i < length; i++) {\n credentialProvider.addOrUpdateServer(list1, list2[i]);\n }\n\n return list1;\n}\n\nfunction updateServerInfo(server, systemInfo) {\n server.Name = systemInfo.ServerName;\n\n if (systemInfo.Id) {\n server.Id = systemInfo.Id;\n }\n if (systemInfo.LocalAddress) {\n server.LocalAddress = systemInfo.LocalAddress;\n }\n}\n\nfunction getEmbyServerUrl(baseUrl, handler) {\n return `${baseUrl}/${handler}`;\n}\n\nfunction getFetchPromise(request) {\n const headers = request.headers || {};\n\n if (request.dataType === 'json') {\n headers.accept = 'application/json';\n }\n\n const fetchRequest = {\n headers,\n method: request.type,\n credentials: 'same-origin'\n };\n\n let contentType = request.contentType;\n\n if (request.data) {\n if (typeof request.data === 'string') {\n fetchRequest.body = request.data;\n } else {\n fetchRequest.body = paramsToString(request.data);\n\n contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';\n }\n }\n\n if (contentType) {\n headers['Content-Type'] = contentType;\n }\n\n if (!request.timeout) {\n return fetch(request.url, fetchRequest);\n }\n\n return fetchWithTimeout(request.url, fetchRequest, request.timeout);\n}\n\nfunction fetchWithTimeout(url, options, timeoutMs) {\n console.log(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`);\n\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(reject, timeoutMs);\n\n options = options || {};\n options.credentials = 'same-origin';\n\n fetch(url, options).then(\n (response) => {\n clearTimeout(timeout);\n\n console.log(`fetchWithTimeout: succeeded connecting to url: ${url}`);\n\n resolve(response);\n },\n (error) => {\n clearTimeout(timeout);\n\n console.log(`fetchWithTimeout: timed out connecting to url: ${url}`);\n\n reject();\n }\n );\n });\n}\n\nfunction ajax(request) {\n if (!request) {\n throw new Error('Request cannot be null');\n }\n\n request.headers = request.headers || {};\n\n console.log(`ConnectionManager requesting url: ${request.url}`);\n\n return getFetchPromise(request).then(\n (response) => {\n console.log(`ConnectionManager response status: ${response.status}, url: ${request.url}`);\n\n if (response.status < 400) {\n if (request.dataType === 'json' || request.headers.accept === 'application/json') {\n return response.json();\n } else {\n return response;\n }\n } else {\n return Promise.reject(response);\n }\n },\n (err) => {\n console.log(`ConnectionManager request failed to url: ${request.url}`);\n throw err;\n }\n );\n}\n\nfunction replaceAll(originalString, strReplace, strWith) {\n const reg = new RegExp(strReplace, 'ig');\n return originalString.replace(reg, strWith);\n}\n\nfunction normalizeAddress(address) {\n // Attempt to correct bad input\n address = address.trim();\n\n // Seeing failures in iOS when protocol isn't lowercase\n address = replaceAll(address, 'Http:', 'http:');\n address = replaceAll(address, 'Https:', 'https:');\n\n return address;\n}\n\nfunction stringEqualsIgnoreCase(str1, str2) {\n return (str1 || '').toLowerCase() === (str2 || '').toLowerCase();\n}\n\nfunction compareVersions(a, b) {\n // -1 a is smaller\n // 1 a is larger\n // 0 equal\n a = a.split('.');\n b = b.split('.');\n\n for (let i = 0, length = Math.max(a.length, b.length); i < length; i++) {\n const aVal = parseInt(a[i] || '0');\n const bVal = parseInt(b[i] || '0');\n\n if (aVal < bVal) {\n return -1;\n }\n\n if (aVal > bVal) {\n return 1;\n }\n }\n\n return 0;\n}\n\nexport default class ConnectionManager {\n constructor(credentialProvider, appName, appVersion, deviceName, deviceId, capabilities) {\n console.log('Begin ConnectionManager constructor');\n\n const self = this;\n this._apiClients = [];\n\n self._minServerVersion = '3.2.33';\n\n self.appVersion = () => appVersion;\n\n self.appName = () => appName;\n\n self.capabilities = () => capabilities;\n\n self.deviceId = () => deviceId;\n\n self.credentialProvider = () => credentialProvider;\n\n self.getServerInfo = (id) => {\n const servers = credentialProvider.credentials().Servers;\n\n return servers.filter((s) => s.Id === id)[0];\n };\n\n self.getLastUsedServer = () => {\n const servers = credentialProvider.credentials().Servers;\n\n servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));\n\n if (!servers.length) {\n return null;\n }\n\n return servers[0];\n };\n\n self.addApiClient = (apiClient) => {\n self._apiClients.push(apiClient);\n\n const existingServers = credentialProvider\n .credentials()\n .Servers.filter(\n (s) =>\n stringEqualsIgnoreCase(s.ManualAddress, apiClient.serverAddress()) ||\n stringEqualsIgnoreCase(s.LocalAddress, apiClient.serverAddress()) ||\n stringEqualsIgnoreCase(s.RemoteAddress, apiClient.serverAddress())\n );\n\n const existingServer = existingServers.length ? existingServers[0] : apiClient.serverInfo();\n existingServer.DateLastAccessed = new Date().getTime();\n existingServer.LastConnectionMode = ConnectionMode.Manual;\n existingServer.ManualAddress = apiClient.serverAddress();\n\n if (apiClient.manualAddressOnly) {\n existingServer.manualAddressOnly = true;\n }\n\n apiClient.serverInfo(existingServer);\n\n apiClient.onAuthenticated = (instance, result) => onAuthenticated(instance, result, {}, true);\n\n if (!existingServers.length) {\n const credentials = credentialProvider.credentials();\n credentials.Servers = [existingServer];\n credentialProvider.credentials(credentials);\n }\n\n events.trigger(self, 'apiclientcreated', [apiClient]);\n };\n\n self.clearData = () => {\n console.log('connection manager clearing data');\n\n const credentials = credentialProvider.credentials();\n credentials.Servers = [];\n credentialProvider.credentials(credentials);\n };\n\n self._getOrAddApiClient = (server, serverUrl) => {\n let apiClient = self.getApiClient(server.Id);\n\n if (!apiClient) {\n apiClient = new ApiClient(serverUrl, appName, appVersion, deviceName, deviceId);\n\n self._apiClients.push(apiClient);\n\n apiClient.serverInfo(server);\n\n apiClient.onAuthenticated = (instance, result) => {\n return onAuthenticated(instance, result, {}, true);\n };\n\n events.trigger(self, 'apiclientcreated', [apiClient]);\n }\n\n console.log('returning instance from getOrAddApiClient');\n return apiClient;\n };\n\n self.getOrCreateApiClient = (serverId) => {\n const credentials = credentialProvider.credentials();\n const servers = credentials.Servers.filter((s) => stringEqualsIgnoreCase(s.Id, serverId));\n\n if (!servers.length) {\n throw new Error(`Server not found: ${serverId}`);\n }\n\n const server = servers[0];\n\n return self._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode));\n };\n\n function onAuthenticated(apiClient, result, options, saveCredentials) {\n const credentials = credentialProvider.credentials();\n const servers = credentials.Servers.filter((s) => s.Id === result.ServerId);\n\n const server = servers.length ? servers[0] : apiClient.serverInfo();\n\n if (options.updateDateLastAccessed !== false) {\n server.DateLastAccessed = new Date().getTime();\n }\n server.Id = result.ServerId;\n\n if (saveCredentials) {\n server.UserId = result.User.Id;\n server.AccessToken = result.AccessToken;\n } else {\n server.UserId = null;\n server.AccessToken = null;\n }\n\n credentialProvider.addOrUpdateServer(credentials.Servers, server);\n credentialProvider.credentials(credentials);\n\n // set this now before updating server info, otherwise it won't be set in time\n apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;\n\n apiClient.serverInfo(server);\n afterConnected(apiClient, options);\n\n return onLocalUserSignIn(server, apiClient.serverAddress(), result.User);\n }\n\n function afterConnected(apiClient, options = {}) {\n if (options.reportCapabilities !== false) {\n apiClient.reportCapabilities(capabilities);\n }\n apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;\n\n if (options.enableWebSocket !== false) {\n console.log('calling apiClient.ensureWebSocket');\n\n apiClient.ensureWebSocket();\n }\n }\n\n function onLocalUserSignIn(server, serverUrl, user) {\n // Ensure this is created so that listeners of the event can get the apiClient instance\n self._getOrAddApiClient(server, serverUrl);\n\n // This allows the app to have a single hook that fires before any other\n const promise = self.onLocalUserSignedIn ? self.onLocalUserSignedIn.call(self, user) : Promise.resolve();\n\n return promise.then(() => {\n events.trigger(self, 'localusersignedin', [user]);\n });\n }\n\n function validateAuthentication(server, serverUrl) {\n return ajax({\n type: 'GET',\n url: getEmbyServerUrl(serverUrl, 'System/Info'),\n dataType: 'json',\n headers: {\n 'X-MediaBrowser-Token': server.AccessToken\n }\n }).then(\n (systemInfo) => {\n updateServerInfo(server, systemInfo);\n return Promise.resolve();\n },\n () => {\n server.UserId = null;\n server.AccessToken = null;\n return Promise.resolve();\n }\n );\n }\n\n function getImageUrl(localUser) {\n if (localUser && localUser.PrimaryImageTag) {\n const apiClient = self.getApiClient(localUser);\n\n const url = apiClient.getUserImageUrl(localUser.Id, {\n tag: localUser.PrimaryImageTag,\n type: 'Primary'\n });\n\n return {\n url,\n supportsParams: true\n };\n }\n\n return {\n url: null,\n supportsParams: false\n };\n }\n\n self.user = (apiClient) =>\n new Promise((resolve, reject) => {\n let localUser;\n\n function onLocalUserDone(e) {\n if (apiClient && apiClient.getCurrentUserId()) {\n apiClient.getCurrentUser().then((u) => {\n localUser = u;\n const image = getImageUrl(localUser);\n\n resolve({\n localUser,\n name: localUser ? localUser.Name : null,\n imageUrl: image.url,\n supportsImageParams: image.supportsParams\n });\n });\n }\n }\n\n if (apiClient && apiClient.getCurrentUserId()) {\n onLocalUserDone();\n }\n });\n\n self.logout = () => {\n const promises = [];\n\n for (let i = 0, length = self._apiClients.length; i < length; i++) {\n const apiClient = self._apiClients[i];\n\n if (apiClient.accessToken()) {\n promises.push(logoutOfServer(apiClient));\n }\n }\n\n return Promise.all(promises).then(() => {\n const credentials = credentialProvider.credentials();\n\n const servers = credentials.Servers.filter((u) => u.UserLinkType !== 'Guest');\n\n for (let j = 0, numServers = servers.length; j < numServers; j++) {\n const server = servers[j];\n\n server.UserId = null;\n server.AccessToken = null;\n server.ExchangeToken = null;\n }\n });\n };\n\n function logoutOfServer(apiClient) {\n const serverInfo = apiClient.serverInfo() || {};\n\n const logoutInfo = {\n serverId: serverInfo.Id\n };\n\n return apiClient.logout().then(\n () => {\n events.trigger(self, 'localusersignedout', [logoutInfo]);\n },\n () => {\n events.trigger(self, 'localusersignedout', [logoutInfo]);\n }\n );\n }\n\n self.getSavedServers = () => {\n const credentials = credentialProvider.credentials();\n\n const servers = credentials.Servers.slice(0);\n\n servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));\n\n return servers;\n };\n\n self.getAvailableServers = () => {\n console.log('Begin getAvailableServers');\n\n // Clone the array\n const credentials = credentialProvider.credentials();\n\n return Promise.all([findServers()]).then((responses) => {\n const foundServers = responses[0];\n let servers = credentials.Servers.slice(0);\n mergeServers(credentialProvider, servers, foundServers);\n\n servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));\n credentials.Servers = servers;\n credentialProvider.credentials(credentials);\n\n return servers;\n });\n };\n\n function findServers() {\n return new Promise((resolve, reject) => {\n var onFinish = function (foundServers) {\n var servers = foundServers.map((foundServer) => {\n var info = {\n Id: foundServer.Id,\n LocalAddress: convertEndpointAddressToManualAddress(foundServer) || foundServer.Address,\n Name: foundServer.Name\n };\n info.LastConnectionMode = info.ManualAddress ? ConnectionMode.Manual : ConnectionMode.Local;\n return info;\n });\n resolve(servers);\n };\n\n if (window.NativeShell && typeof window.NativeShell.findServers === 'function') {\n window.NativeShell.findServers(1e3).then(onFinish, function () {\n onFinish([]);\n });\n } else {\n resolve([]);\n }\n });\n }\n\n function convertEndpointAddressToManualAddress(info) {\n if (info.Address && info.EndpointAddress) {\n let address = info.EndpointAddress.split(':')[0];\n\n // Determine the port, if any\n const parts = info.Address.split(':');\n if (parts.length > 1) {\n const portString = parts[parts.length - 1];\n\n if (!isNaN(parseInt(portString))) {\n address += `:${portString}`;\n }\n }\n\n return normalizeAddress(address);\n }\n\n return null;\n }\n\n self.connectToServers = (servers, options) => {\n console.log(`Begin connectToServers, with ${servers.length} servers`);\n\n const firstServer = servers.length ? servers[0] : null;\n // See if we have any saved credentials and can auto sign in\n if (firstServer) {\n return self.connectToServer(firstServer, options).then((result) => {\n if (result.State === 'Unavailable') {\n result.State = 'ServerSelection';\n }\n\n console.log('resolving connectToServers with result.State: ' + result.State);\n return result;\n });\n }\n\n return Promise.resolve({\n Servers: servers,\n State: 'ServerSelection'\n });\n };\n\n function getTryConnectPromise(url, connectionMode, state, resolve, reject) {\n console.log('getTryConnectPromise ' + url);\n\n ajax({\n url: getEmbyServerUrl(url, 'system/info/public'),\n timeout: defaultTimeout,\n type: 'GET',\n dataType: 'json'\n }).then(\n (result) => {\n if (!state.resolved) {\n state.resolved = true;\n\n console.log('Reconnect succeeded to ' + url);\n resolve({\n url: url,\n connectionMode: connectionMode,\n data: result\n });\n }\n },\n () => {\n console.log('Reconnect failed to ' + url);\n\n if (!state.resolved) {\n state.rejects++;\n if (state.rejects >= state.numAddresses) {\n reject();\n }\n }\n }\n );\n }\n\n function tryReconnect(serverInfo) {\n const addresses = [];\n const addressesStrings = [];\n\n // the timeouts are a small hack to try and ensure the remote address doesn't resolve first\n\n // manualAddressOnly is used for the local web app that always connects to a fixed address\n if (\n !serverInfo.manualAddressOnly &&\n serverInfo.LocalAddress &&\n addressesStrings.indexOf(serverInfo.LocalAddress) === -1\n ) {\n addresses.push({\n url: serverInfo.LocalAddress,\n mode: ConnectionMode.Local,\n timeout: 0\n });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (serverInfo.ManualAddress && addressesStrings.indexOf(serverInfo.ManualAddress) === -1) {\n addresses.push({\n url: serverInfo.ManualAddress,\n mode: ConnectionMode.Manual,\n timeout: 100\n });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (\n !serverInfo.manualAddressOnly &&\n serverInfo.RemoteAddress &&\n addressesStrings.indexOf(serverInfo.RemoteAddress) === -1\n ) {\n addresses.push({\n url: serverInfo.RemoteAddress,\n mode: ConnectionMode.Remote,\n timeout: 200\n });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n\n console.log('tryReconnect: ' + addressesStrings.join('|'));\n\n return new Promise((resolve, reject) => {\n const state = {};\n state.numAddresses = addresses.length;\n state.rejects = 0;\n\n addresses.map((url) => {\n setTimeout(() => {\n if (!state.resolved) {\n getTryConnectPromise(url.url, url.mode, state, resolve, reject);\n }\n }, url.timeout);\n });\n });\n }\n\n self.connectToServer = (server, options) => {\n console.log('begin connectToServer');\n\n return new Promise((resolve, reject) => {\n options = options || {};\n\n tryReconnect(server).then(\n (result) => {\n const serverUrl = result.url;\n const connectionMode = result.connectionMode;\n result = result.data;\n\n if (compareVersions(self.minServerVersion(), result.Version) === 1) {\n console.log('minServerVersion requirement not met. Server version: ' + result.Version);\n resolve({\n State: 'ServerUpdateNeeded',\n Servers: [server]\n });\n } else if (server.Id && result.Id !== server.Id) {\n console.log(\n 'http request succeeded, but found a different server Id than what was expected'\n );\n resolveFailure(self, resolve);\n } else {\n onSuccessfulConnection(server, result, connectionMode, serverUrl, true, resolve, options);\n }\n },\n () => {\n resolveFailure(self, resolve);\n }\n );\n });\n };\n\n function onSuccessfulConnection(server, systemInfo, connectionMode, serverUrl, verifyLocalAuthentication, resolve, options={}) {\n const credentials = credentialProvider.credentials();\n\n if (options.enableAutoLogin === false) {\n server.UserId = null;\n server.AccessToken = null;\n } else if (server.AccessToken && verifyLocalAuthentication) {\n return void validateAuthentication(server, serverUrl).then(function () {\n onSuccessfulConnection(server, systemInfo, connectionMode, serverUrl, false, resolve, options);\n });\n }\n\n updateServerInfo(server, systemInfo);\n\n server.LastConnectionMode = connectionMode;\n\n if (options.updateDateLastAccessed !== false) {\n server.DateLastAccessed = new Date().getTime();\n }\n credentialProvider.addOrUpdateServer(credentials.Servers, server);\n credentialProvider.credentials(credentials);\n\n const result = {\n Servers: []\n };\n\n result.ApiClient = self._getOrAddApiClient(server, serverUrl);\n\n result.ApiClient.setSystemInfo(systemInfo);\n\n result.State = server.AccessToken && options.enableAutoLogin !== false ? 'SignedIn' : 'ServerSignIn';\n\n result.Servers.push(server);\n\n // set this now before updating server info, otherwise it won't be set in time\n result.ApiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;\n\n result.ApiClient.updateServerInfo(server, serverUrl);\n\n const resolveActions = function () {\n resolve(result);\n\n events.trigger(self, 'connected', [result]);\n };\n\n if (result.State === 'SignedIn') {\n afterConnected(result.ApiClient, options);\n\n result.ApiClient.getCurrentUser().then((user) => {\n onLocalUserSignIn(server, serverUrl, user).then(resolveActions, resolveActions);\n }, resolveActions);\n } else {\n resolveActions();\n }\n }\n\n function tryConnectToAddress(address, options) {\n const server = {\n ManualAddress: address,\n LastConnectionMode: ConnectionMode.Manual\n };\n\n return self.connectToServer(server, options).then((result) => {\n // connectToServer never rejects, but resolves with State='Unavailable'\n if (result.State === 'Unavailable') {\n return Promise.reject();\n }\n return result;\n });\n }\n\n self.connectToAddress = function (address, options) {\n if (!address) {\n return Promise.reject();\n }\n\n address = normalizeAddress(address);\n\n let urls = [];\n\n if (/^[^:]+:\\/\\//.test(address)) {\n // Protocol specified - connect as is\n urls.push(address);\n } else {\n urls.push(`https://${address}`);\n urls.push(`http://${address}`);\n }\n\n let i = 0;\n\n function onFail() {\n console.log(`connectToAddress ${urls[i]} failed`);\n\n if (++i < urls.length) {\n return tryConnectToAddress(urls[i], options).catch(onFail);\n }\n\n return Promise.resolve({\n State: 'Unavailable'\n });\n }\n\n return tryConnectToAddress(urls[i], options).catch(onFail);\n };\n\n self.deleteServer = (serverId) => {\n if (!serverId) {\n throw new Error('null serverId');\n }\n\n let server = credentialProvider.credentials().Servers.filter((s) => s.Id === serverId);\n server = server.length ? server[0] : null;\n\n return new Promise((resolve, reject) => {\n function onDone() {\n const credentials = credentialProvider.credentials();\n\n credentials.Servers = credentials.Servers.filter((s) => s.Id !== serverId);\n\n credentialProvider.credentials(credentials);\n resolve();\n }\n\n if (!server.ConnectServerId) {\n onDone();\n return;\n }\n });\n };\n }\n\n connect(options) {\n console.log('Begin connect');\n\n return this.getAvailableServers().then((servers) => {\n return this.connectToServers(servers, options);\n });\n }\n\n handleMessageReceived(msg) {\n const serverId = msg.ServerId;\n if (serverId) {\n const apiClient = this.getApiClient(serverId);\n if (apiClient) {\n if (typeof msg.Data === 'string') {\n try {\n msg.Data = JSON.parse(msg.Data);\n } catch (err) {\n console.log('unable to parse json content: ' + err);\n }\n }\n\n apiClient.handleMessageReceived(msg);\n }\n }\n }\n\n getApiClients() {\n const servers = this.getSavedServers();\n\n for (let i = 0, length = servers.length; i < length; i++) {\n const server = servers[i];\n if (server.Id) {\n this._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode));\n }\n }\n\n return this._apiClients;\n }\n\n getApiClient(item) {\n if (!item) {\n throw new Error('item or serverId cannot be null');\n }\n\n // Accept string + object\n if (item.ServerId) {\n item = item.ServerId;\n }\n\n return this._apiClients.filter((a) => {\n const serverInfo = a.serverInfo();\n\n // We have to keep this hack in here because of the addApiClient method\n return !serverInfo || serverInfo.Id === item;\n })[0];\n }\n\n minServerVersion(val) {\n if (val) {\n this._minServerVersion = val;\n }\n\n return this._minServerVersion;\n }\n}\n","import events from './events';\nimport appStorage from './appStorage';\n\nfunction ensure(instance, data) {\n if (!instance._credentials) {\n const json = instance.appStorage.getItem(instance.key) || '{}';\n\n console.log(`credentials initialized with: ${json}`);\n instance._credentials = JSON.parse(json);\n instance._credentials.Servers = instance._credentials.Servers || [];\n }\n}\n\nfunction set(instance, data) {\n if (data) {\n instance._credentials = data;\n instance.appStorage.setItem(instance.key, JSON.stringify(data));\n } else {\n instance.clear();\n }\n\n events.trigger(instance, 'credentialsupdated');\n}\n\nexport default class Credentials {\n constructor(key) {\n this.key = key || 'jellyfin_credentials';\n this.appStorage = appStorage;\n }\n\n clear() {\n this._credentials = null;\n this.appStorage.removeItem(this.key);\n }\n\n credentials(data) {\n if (data) {\n set(this, data);\n }\n\n ensure(this);\n return this._credentials;\n }\n\n addOrUpdateServer(list, server) {\n if (!server.Id) {\n throw new Error('Server.Id cannot be null or empty');\n }\n\n const existing = list.filter(({ Id }) => Id === server.Id)[0];\n\n if (existing) {\n // Merge the data\n existing.DateLastAccessed = Math.max(existing.DateLastAccessed || 0, server.DateLastAccessed || 0);\n\n existing.UserLinkType = server.UserLinkType;\n\n if (server.AccessToken) {\n existing.AccessToken = server.AccessToken;\n existing.UserId = server.UserId;\n }\n if (server.ExchangeToken) {\n existing.ExchangeToken = server.ExchangeToken;\n }\n if (server.RemoteAddress) {\n existing.RemoteAddress = server.RemoteAddress;\n }\n if (server.ManualAddress) {\n existing.ManualAddress = server.ManualAddress;\n }\n if (server.LocalAddress) {\n existing.LocalAddress = server.LocalAddress;\n }\n if (server.Name) {\n existing.Name = server.Name;\n }\n if (server.LastConnectionMode != null) {\n existing.LastConnectionMode = server.LastConnectionMode;\n }\n if (server.ConnectServerId) {\n existing.ConnectServerId = server.ConnectServerId;\n }\n\n return existing;\n } else {\n list.push(server);\n return server;\n }\n }\n}\n","import ApiClient from './apiClient';\nimport ApiClientCore from './apiClientCore';\nimport AppStorage from './appStorage';\nimport ConnectionManager from './connectionManager';\nimport Credentials from './credentials';\nimport Events from './events';\n\nexport default {\n ApiClient,\n ApiClientCore,\n AppStorage,\n ConnectionManager,\n Credentials,\n Events\n};\n"],"sourceRoot":""} +\ No newline at end of file ++{"version":3,"sources":["webpack://[name]/webpack/universalModuleDefinition","webpack://[name]/webpack/bootstrap","webpack://[name]/./src/events.js","webpack://[name]/./src/appStorage.js","webpack://[name]/./src/apiClient.js","webpack://[name]/./src/apiClientCore.js","webpack://[name]/./src/connectionManager.js","webpack://[name]/./src/credentials.js","webpack://[name]/./src/index.js"],"names":["root","factory","exports","module","define","amd","window","installedModules","__webpack_require__","moduleId","i","l","modules","call","m","c","d","name","getter","o","Object","defineProperty","enumerable","get","r","Symbol","toStringTag","value","t","mode","__esModule","ns","create","key","bind","n","object","property","prototype","hasOwnProperty","p","s","getCallbacks","obj","Error","_callbacks","list","on","eventName","fn","push","off","indexOf","splice","trigger","eventObject","type","eventArgs","additionalArgs","arguments","length","callbacks","slice","forEach","apply","onCachePutFail","e","console","log","updateCache","instance","cache","put","Response","JSON","stringify","localData","catch","onCacheOpened","result","this","AppStore","self","caches","open","then","err","localStorage","setItem","getItem","removeItem","getInstance","reportRateLimits","timeupdate","volumechange","redetectBitrate","stopBitrateDetection","accessToken","enableAutomaticBitrateDetection","setTimeout","redetectBitrateInternal","detectBitrate","detectTimeout","clearTimeout","replaceAll","originalString","strReplace","strWith","reg","RegExp","replace","onFetchFail","url","response","events","status","errorCode","headers","paramsToString","params","values","encodeURIComponent","join","fetchWithTimeout","options","timeoutMs","Promise","resolve","reject","timeout","credentials","fetch","error","getFetchPromise","request","dataType","accept","fetchRequest","method","contentType","data","body","cancelReportPlaybackProgressPromise","reportPlaybackProgressCancel","setSavedEndpointInfo","info","_endPointInfo","getCachedUser","userId","serverId","json","appStorage","parse","onWebSocketMessage","msg","onMessageReceivedInternal","messageIdsReceived","messageId","MessageId","MessageType","_currentUser","Data","Id","getCurrentUserId","debug","sendWebSocketMessage","clearKeepAlive","keepAliveInterval","setInterval","scheduleKeepAlive","clearInterval","onWebSocketOpen","onWebSocketError","normalizeReturnBitrate","bitrate","lastDetectedBitrate","Math","round","getMaxBandwidth","maxRate","min","lastDetectedBitrateTime","Date","getTime","detectBitrateWithEndpointInfo","endpointInfo","IsInNetwork","detectBitrateInternal","tests","index","currentBitrate","test","getDownloadSpeed","bytes","threshold","getRemoteImagePrefix","urlPrefix","artist","encodeName","person","genre","musicGenre","studio","itemId","normalizeImageOptions","ratio","devicePixelRatio","minScale","max","width","height","maxWidth","maxHeight","quality","getDefaultImageQuality","ApiClient","serverAddress","appName","appVersion","deviceName","deviceId","_serverInfo","_serverAddress","_deviceId","_deviceName","_appName","_appVersion","currentServerInfo","serverInfo","AccessToken","auth","val","toLowerCase","changed","onNetworkChange","charAt","enableReconnection","lastFetch","text","toString","previousServerAddress","tryReconnect","retryCount","addresses","addressesStrings","LocalAddress","ManualAddress","RemoteAddress","state","numAddresses","rejects","map","resolved","getUrl","getTryConnectPromise","tryReconnectInternal","fetchWithFailover","innerError","includeAuthorization","setRequestHeaders","enableAutomaticNetworking","accessKey","UserId","Name","enableCache","user","serverPromise","getUser","userObject","ServerId","closeWebSocket","done","setAuthenticationInfo","ajax","password","postData","Username","Pw","afterOnAuthenticated","onAuthenticated","isWebSocketOpenOrConnecting","isWebSocketSupported","openWebSocket","apiClient","socket","webSocket","WebSocket","onmessage","onopen","onerror","onclose","_webSocket","readyState","OPEN","close","send","isWebSocketOpen","CONNECTING","server","serverUrl","split","substring","byteSize","Size","now","responseTimeSeconds","bytesPerSecond","force","getEndpointInfo","getJSON","idList","isRead","suffix","Ids","id","channelIds","item","feature","setSystemInfo","TargetId","app","client","deviceProfile","DeviceProfile","liveStreamId","LiveStreamId","path","installationId","guid","version","AssemblyGuid","DeviceId","PackageType","refreshLibrary","libraryOptions","collectionType","LibraryOptions","newName","virtualFolderName","mediaPath","networkSharePath","pathInfo","Path","NetworkPath","PathInfo","imageType","imageIndex","playSessionId","PlaySessionId","newIndex","api_key","file","startsWith","reader","FileReader","onabort","onload","target","lastIndexOf","readAsDataURL","ImageTags","Thumb","tag","getImageUrl","ParentThumbItemId","ParentThumbImageTag","currentPassword","newPassword","CurrentPw","NewPw","configuration","policy","triggers","isMinServerVersion","getItems","assign","SortBy","SortOrder","Filters","Recursive","CollapseBoxSetItems","ExcludeLocationTypes","inherit","InheritFromParent","SearchHints","date","formatDigit","getFullYear","getMonth","getDate","getHours","getMinutes","getSeconds","DatePlayed","getDateParamValue","isFavorite","likes","lastPlaybackProgressReport","lastPlaybackProgressReportTicks","EventName","reportRateLimitTime","msSinceLastReport","newPositionTicks","PositionTicks","expectedReportTicks","abs","undefined","reportPlaybackProgressTimeout","lastPlaybackProgressOptions","reportPlaybackProgressPromise","promise","cancelled","resetPromise","delay","lastOptions","sendReport","finally","actions","syncJobItemId","itemIds","targetId","ItemIds","sessionId","command","ajaxOptions","review","packageId","minRating","maxRating","limit","MinRating","MaxRating","Limit","savedValue","endPointInfo","_serverVersion","Version","serverVersion","a","b","aVal","parseInt","bVal","compareVersions","isLocalId","str","isLocalViewId","isTopLevelLocalViewId","stripLocalPrefix","res","stripStart","find","substr","convertGuidToLocal","adjustGuidProperties","downloadedItem","SeriesId","SeasonId","AlbumId","ParentId","ParentPrimaryImageItemId","PrimaryImageItemId","ParentLogoItemId","ParentBackdropItemId","ParentBackdropImageTags","getLocalView","getLocalFolders","views","localView","downloadsTitleText","Type","IsFolder","ApiClientCore","clientName","applicationVersion","localAssetManager","onFailure","getPlaybackInfo","getLocalItem","MediaSources","Item","SupportsDirectPlay","SupportsDirectStream","SupportsTranscoding","IsLocal","mediaSources","fileExists","LocalPath","exists","items","Items","TotalRecordCount","AlbumIds","getViewItems","ExcludeItemIds","exItems","ids","hasLocal","getItemsFromIds","basePromise","getUserViews","enableLocalView","filter","getViews","getNextUpEpisodes","IncludeItemTypes","getSeasons","seasonId","getEpisodes","getThemeMedia","getSpecialFeatures","getSimilarItems","updateFavoriteStatus","itemid","getScaledImageUrl","ItemId","reportPlaybackStart","libraryItem","MediaType","UserData","PlaybackPositionTicks","PlayedPercentage","RunTimeTicks","addOrUpdateLocalItem","reportPlaybackProgress","action","recordUserAction","reportPlaybackStopped","getIntros","getInstantMixFromItem","getItemDownloadUrl","ConnectionMode","getServerAddress","resolveFailure","State","updateServerInfo","systemInfo","ServerName","getEmbyServerUrl","baseUrl","handler","normalizeAddress","address","trim","stringEqualsIgnoreCase","str1","str2","ConnectionManager","credentialProvider","capabilities","saveCredentials","servers","Servers","updateDateLastAccessed","DateLastAccessed","User","addOrUpdateServer","afterConnected","onLocalUserSignIn","reportCapabilities","enableWebSocket","ensureWebSocket","_getOrAddApiClient","onLocalUserSignedIn","validateAuthentication","logoutOfServer","logoutInfo","logout","convertEndpointAddressToManualAddress","Address","EndpointAddress","parts","portString","isNaN","manualAddressOnly","connectionMode","tryConnectToAddress","LastConnectionMode","connectToServer","_apiClients","_minServerVersion","getServerInfo","getLastUsedServer","sort","addApiClient","existingServers","existingServer","clearData","getApiClient","getOrCreateApiClient","localUser","getCurrentUser","u","image","PrimaryImageTag","getUserImageUrl","supportsParams","imageUrl","supportsImageParams","promises","all","UserLinkType","j","numServers","ExchangeToken","getSavedServers","getAvailableServers","onFinish","foundServers","foundServer","NativeShell","findServers","responses","list1","list2","mergeServers","connectToServers","firstServer","minServerVersion","onSuccessfulConnection","verifyLocalAuthentication","enableAutoLogin","resolveActions","connectToAddress","urls","onFail","deleteServer","ConnectServerId","handleMessageReceived","Credentials","_credentials","clear","set","ensure","existing","AppStorage","Events"],"mappings":"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","file":"jellyfin-apiclient.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"jellyfin-apiclient\"] = factory();\n\telse\n\t\troot[\"jellyfin-apiclient\"] = factory();\n})(window, function() {\nreturn "," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(__webpack_require__.s = 0);\n","function getCallbacks(obj, name) {\n if (!obj) {\n throw new Error('obj cannot be null!');\n }\n\n obj._callbacks = obj._callbacks || {};\n\n let list = obj._callbacks[name];\n\n if (!list) {\n obj._callbacks[name] = [];\n list = obj._callbacks[name];\n }\n\n return list;\n}\n\nexport default {\n on(obj, eventName, fn) {\n const list = getCallbacks(obj, eventName);\n\n list.push(fn);\n },\n\n off(obj, eventName, fn) {\n const list = getCallbacks(obj, eventName);\n\n const i = list.indexOf(fn);\n if (i !== -1) {\n list.splice(i, 1);\n }\n },\n\n trigger(obj, eventName) {\n const eventObject = {\n type: eventName\n };\n\n const eventArgs = [];\n eventArgs.push(eventObject);\n\n const additionalArgs = arguments[2] || [];\n for (let i = 0, length = additionalArgs.length; i < length; i++) {\n eventArgs.push(additionalArgs[i]);\n }\n\n const callbacks = getCallbacks(obj, eventName).slice(0);\n\n callbacks.forEach((c) => {\n c.apply(obj, eventArgs);\n });\n }\n};\n","function onCachePutFail(e) {\n console.log(e);\n}\n\nfunction updateCache(instance) {\n const cache = instance.cache;\n if (cache) {\n cache.put('data', new Response(JSON.stringify(instance.localData))).catch(onCachePutFail);\n }\n}\n\nfunction onCacheOpened(result) {\n this.cache = result;\n this.localData = {};\n}\n\nclass AppStore {\n constructor() {\n try {\n if (self.caches) {\n caches.open('embydata').then(onCacheOpened.bind(this));\n }\n } catch (err) {\n console.log(`Error opening cache: ${err}`);\n }\n }\n\n setItem(name, value) {\n localStorage.setItem(name, value);\n const localData = this.localData;\n if (localData) {\n const changed = localData[name] !== value;\n if (changed) {\n localData[name] = value;\n updateCache(this);\n }\n }\n }\n\n static getInstance() {\n if (!AppStore.instance) {\n AppStore.instance = new AppStore();\n }\n\n return AppStore.instance;\n }\n\n getItem(name) {\n return localStorage.getItem(name);\n }\n\n removeItem(name) {\n localStorage.removeItem(name);\n const localData = this.localData;\n if (localData) {\n localData[name] = null;\n delete localData[name];\n updateCache(this);\n }\n }\n}\n\nexport default AppStore.getInstance();\n","import events from './events';\nimport appStorage from './appStorage';\n\n/** Report rate limits in ms for different events */\nconst reportRateLimits = {\n timeupdate: 500,\n volumechange: 3000\n};\n\nfunction redetectBitrate(instance) {\n stopBitrateDetection(instance);\n\n if (instance.accessToken() && instance.enableAutomaticBitrateDetection !== false) {\n setTimeout(redetectBitrateInternal.bind(instance), 6000);\n }\n}\n\nfunction redetectBitrateInternal() {\n if (this.accessToken()) {\n this.detectBitrate();\n }\n}\n\nfunction stopBitrateDetection(instance) {\n if (instance.detectTimeout) {\n clearTimeout(instance.detectTimeout);\n }\n}\n\nfunction replaceAll(originalString, strReplace, strWith) {\n const reg = new RegExp(strReplace, 'ig');\n return originalString.replace(reg, strWith);\n}\n\nfunction onFetchFail(instance, url, response) {\n events.trigger(instance, 'requestfail', [\n {\n url,\n status: response.status,\n errorCode: response.headers ? response.headers.get('X-Application-Error-Code') : null\n }\n ]);\n}\n\nfunction paramsToString(params) {\n const values = [];\n\n for (const key in params) {\n const value = params[key];\n\n if (value !== null && value !== undefined && value !== '') {\n values.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);\n }\n }\n return values.join('&');\n}\n\nfunction fetchWithTimeout(url, options, timeoutMs) {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(reject, timeoutMs);\n\n options = options || {};\n options.credentials = 'same-origin';\n\n fetch(url, options)\n .then((response) => {\n clearTimeout(timeout);\n resolve(response);\n })\n .catch((error) => {\n clearTimeout(timeout);\n reject(error);\n });\n });\n}\n\nfunction getFetchPromise(request) {\n const headers = request.headers || {};\n\n if (request.dataType === 'json') {\n headers.accept = 'application/json';\n }\n\n const fetchRequest = {\n headers,\n method: request.type,\n credentials: 'same-origin'\n };\n\n let contentType = request.contentType;\n\n if (request.data) {\n if (typeof request.data === 'string') {\n fetchRequest.body = request.data;\n } else {\n fetchRequest.body = paramsToString(request.data);\n\n contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';\n }\n }\n\n if (contentType) {\n headers['Content-Type'] = contentType;\n }\n\n if (!request.timeout) {\n return fetch(request.url, fetchRequest);\n }\n\n return fetchWithTimeout(request.url, fetchRequest, request.timeout);\n}\n\nfunction cancelReportPlaybackProgressPromise(instance) {\n if (typeof instance.reportPlaybackProgressCancel === 'function') instance.reportPlaybackProgressCancel();\n}\n\n/**\n * Creates a new api client instance\n * @param {String} serverAddress\n * @param {String} appName\n * @param {String} appVersion\n */\nclass ApiClient {\n constructor(serverAddress, appName, appVersion, deviceName, deviceId) {\n if (!serverAddress) {\n throw new Error('Must supply a serverAddress');\n }\n\n console.debug(`ApiClient serverAddress: ${serverAddress}`);\n console.debug(`ApiClient appName: ${appName}`);\n console.debug(`ApiClient appVersion: ${appVersion}`);\n console.debug(`ApiClient deviceName: ${deviceName}`);\n console.debug(`ApiClient deviceId: ${deviceId}`);\n\n this._serverInfo = {};\n this._serverAddress = serverAddress;\n this._deviceId = deviceId;\n this._deviceName = deviceName;\n this._appName = appName;\n this._appVersion = appVersion;\n }\n\n appName() {\n return this._appName;\n }\n\n setRequestHeaders(headers) {\n const currentServerInfo = this.serverInfo();\n const appName = this._appName;\n const accessToken = currentServerInfo.AccessToken;\n\n const values = [];\n\n if (appName) {\n values.push(`Client=\"${appName}\"`);\n }\n\n if (this._deviceName) {\n values.push(`Device=\"${this._deviceName}\"`);\n }\n\n if (this._deviceId) {\n values.push(`DeviceId=\"${this._deviceId}\"`);\n }\n\n if (this._appVersion) {\n values.push(`Version=\"${this._appVersion}\"`);\n }\n\n if (accessToken) {\n values.push(`Token=\"${accessToken}\"`);\n }\n\n if (values.length) {\n const auth = `MediaBrowser ${values.join(', ')}`;\n //headers.Authorization = auth;\n headers['X-Emby-Authorization'] = auth;\n }\n }\n\n appVersion() {\n return this._appVersion;\n }\n\n deviceName() {\n return this._deviceName;\n }\n\n deviceId() {\n return this._deviceId;\n }\n\n /**\n * Gets the server address.\n */\n serverAddress(val) {\n if (val != null) {\n if (val.toLowerCase().indexOf('http') !== 0) {\n throw new Error(`Invalid url: ${val}`);\n }\n\n const changed = val !== this._serverAddress;\n\n this._serverAddress = val;\n\n this.onNetworkChange();\n\n if (changed) {\n events.trigger(this, 'serveraddresschanged');\n }\n }\n\n return this._serverAddress;\n }\n\n onNetworkChange() {\n this.lastDetectedBitrate = 0;\n this.lastDetectedBitrateTime = 0;\n setSavedEndpointInfo(this, null);\n\n redetectBitrate(this);\n }\n\n /**\n * Creates an api url based on a handler name and query string parameters\n * @param {String} name\n * @param {Object} params\n */\n getUrl(name, params, serverAddress) {\n if (!name) {\n throw new Error('Url name cannot be empty');\n }\n\n let url = serverAddress || this._serverAddress;\n\n if (!url) {\n throw new Error('serverAddress is yet not set');\n }\n\n if (name.charAt(0) !== '/') {\n url += '/';\n }\n\n url += name;\n\n if (params) {\n params = paramsToString(params);\n if (params) {\n url += `?${params}`;\n }\n }\n\n return url;\n }\n\n fetchWithFailover(request, enableReconnection) {\n console.log(`Requesting ${request.url}`);\n\n request.timeout = 30000;\n const instance = this;\n\n return getFetchPromise(request)\n .then((response) => {\n instance.lastFetch = new Date().getTime();\n\n if (response.status < 400) {\n if (request.dataType === 'json' || request.headers.accept === 'application/json') {\n return response.json();\n } else if (\n request.dataType === 'text' ||\n (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0\n ) {\n return response.text();\n } else {\n return response;\n }\n } else {\n onFetchFail(instance, request.url, response);\n return Promise.reject(response);\n }\n })\n .catch((error) => {\n if (error) {\n console.log(`Request failed to ${request.url} ${error.toString()}`);\n } else {\n console.log(`Request timed out to ${request.url}`);\n }\n\n // http://api.jquery.com/jQuery.ajax/\n if ((!error || !error.status) && enableReconnection) {\n console.log('Attempting reconnection');\n\n const previousServerAddress = instance.serverAddress();\n\n return tryReconnect(instance)\n .then(() => {\n console.log('Reconnect succeesed');\n request.url = request.url.replace(previousServerAddress, instance.serverAddress());\n\n return instance.fetchWithFailover(request, false);\n })\n .catch((innerError) => {\n console.log('Reconnect failed');\n onFetchFail(instance, request.url, {});\n throw innerError;\n });\n } else {\n console.log('Reporting request failure');\n\n onFetchFail(instance, request.url, {});\n throw error;\n }\n });\n }\n\n /**\n * Wraps around jQuery ajax methods to add additional info to the request.\n */\n fetch(request, includeAuthorization) {\n if (!request) {\n return Promise.reject('Request cannot be null');\n }\n\n request.headers = request.headers || {};\n\n if (includeAuthorization !== false) {\n this.setRequestHeaders(request.headers);\n }\n\n if (this.enableAutomaticNetworking === false || request.type !== 'GET') {\n console.log(`Requesting url without automatic networking: ${request.url}`);\n\n const instance = this;\n return getFetchPromise(request)\n .then((response) => {\n instance.lastFetch = new Date().getTime();\n\n if (response.status < 400) {\n if (request.dataType === 'json' || request.headers.accept === 'application/json') {\n return response.json();\n } else if (\n request.dataType === 'text' ||\n (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0\n ) {\n return response.text();\n } else {\n return response;\n }\n } else {\n onFetchFail(instance, request.url, response);\n return Promise.reject(response);\n }\n })\n .catch((error) => {\n onFetchFail(instance, request.url, {});\n return Promise.reject(error);\n });\n }\n\n return this.fetchWithFailover(request, true);\n }\n\n setAuthenticationInfo(accessKey, userId) {\n this._currentUser = null;\n\n this._serverInfo.AccessToken = accessKey;\n this._serverInfo.UserId = userId;\n redetectBitrate(this);\n }\n\n serverInfo(info) {\n if (info) {\n this._serverInfo = info;\n }\n\n return this._serverInfo;\n }\n\n /**\n * Gets or sets the current user id.\n */\n getCurrentUserId() {\n return this._serverInfo.UserId;\n }\n\n accessToken() {\n return this._serverInfo.AccessToken;\n }\n\n serverId() {\n return this.serverInfo().Id;\n }\n\n serverName() {\n return this.serverInfo().Name;\n }\n\n /**\n * Wraps around jQuery ajax methods to add additional info to the request.\n */\n ajax(request, includeAuthorization) {\n if (!request) {\n return Promise.reject('Request cannot be null');\n }\n\n return this.fetch(request, includeAuthorization);\n }\n\n /**\n * Gets or sets the current user id.\n */\n getCurrentUser(enableCache) {\n if (this._currentUser) {\n return Promise.resolve(this._currentUser);\n }\n\n const userId = this.getCurrentUserId();\n\n if (!userId) {\n return Promise.reject();\n }\n\n const instance = this;\n let user;\n\n const serverPromise = this.getUser(userId)\n .then((userObject) => {\n appStorage.setItem(`user-${userObject.Id}-${userObject.ServerId}`, JSON.stringify(userObject));\n\n instance._currentUser = userObject;\n return userObject;\n })\n .catch((response) => {\n // if timed out, look for cached value\n if (!response.status) {\n if (userId && instance.accessToken()) {\n user = getCachedUser(instance, userId);\n if (user) {\n return Promise.resolve(user);\n }\n }\n }\n\n throw response;\n });\n\n if (!this.lastFetch && enableCache !== false) {\n user = getCachedUser(instance, userId);\n if (user) {\n return Promise.resolve(user);\n }\n }\n\n return serverPromise;\n }\n\n isLoggedIn() {\n const info = this.serverInfo();\n if (info) {\n if (info.UserId && info.AccessToken) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Logout current user\n */\n logout() {\n stopBitrateDetection(this);\n this.closeWebSocket();\n\n const done = () => {\n const info = this.serverInfo();\n if (info && info.UserId && info.Id) {\n appStorage.removeItem(`user-${info.UserId}-${info.Id}`);\n }\n this.setAuthenticationInfo(null, null);\n };\n\n if (this.accessToken()) {\n const url = this.getUrl('Sessions/Logout');\n\n return this.ajax({\n type: 'POST',\n url\n }).then(done, done);\n }\n\n done();\n return Promise.resolve();\n }\n\n /**\n * Authenticates a user\n * @param {String} name\n * @param {String} password\n */\n authenticateUserByName(name, password) {\n if (!name) {\n return Promise.reject();\n }\n\n const url = this.getUrl('Users/authenticatebyname');\n\n return new Promise((resolve, reject) => {\n const postData = {\n Username: name,\n Pw: password || ''\n };\n\n this.ajax({\n type: 'POST',\n url: url,\n data: JSON.stringify(postData),\n dataType: 'json',\n contentType: 'application/json'\n })\n .then((result) => {\n const afterOnAuthenticated = () => {\n redetectBitrate(this);\n resolve(result);\n };\n\n if (this.onAuthenticated) {\n this.onAuthenticated(this, result).then(afterOnAuthenticated);\n } else {\n afterOnAuthenticated();\n }\n })\n .catch(reject);\n });\n }\n\n ensureWebSocket() {\n if (this.isWebSocketOpenOrConnecting() || !this.isWebSocketSupported()) {\n return;\n }\n\n try {\n this.openWebSocket();\n } catch (err) {\n console.log(`Error opening web socket: ${err}`);\n }\n }\n\n openWebSocket() {\n const accessToken = this.accessToken();\n\n if (!accessToken) {\n throw new Error('Cannot open web socket without access token.');\n }\n\n let url = this.getUrl('socket');\n\n url = replaceAll(url, 'emby/socket', 'embywebsocket');\n url = replaceAll(url, 'https:', 'wss:');\n url = replaceAll(url, 'http:', 'ws:');\n\n url += `?api_key=${accessToken}`;\n url += `&deviceId=${this.deviceId()}`;\n\n console.log(`opening web socket with url: ${url}`);\n\n const webSocket = new WebSocket(url);\n\n webSocket.onmessage = onWebSocketMessage.bind(this);\n webSocket.onopen = onWebSocketOpen.bind(this);\n webSocket.onerror = onWebSocketError.bind(this);\n setSocketOnClose(this, webSocket);\n\n this._webSocket = webSocket;\n }\n\n closeWebSocket() {\n const socket = this._webSocket;\n\n if (socket && socket.readyState === WebSocket.OPEN) {\n socket.close();\n }\n }\n\n sendWebSocketMessage(name, data) {\n console.log(`Sending web socket message: ${name}`);\n\n let msg = { MessageType: name };\n\n if (data) {\n msg.Data = data;\n }\n\n msg = JSON.stringify(msg);\n\n this._webSocket.send(msg);\n }\n\n sendMessage(name, data) {\n if (this.isWebSocketOpen()) {\n this.sendWebSocketMessage(name, data);\n }\n }\n\n isMessageChannelOpen() {\n return this.isWebSocketOpen();\n }\n\n isWebSocketOpen() {\n const socket = this._webSocket;\n\n if (socket) {\n return socket.readyState === WebSocket.OPEN;\n }\n return false;\n }\n\n isWebSocketOpenOrConnecting() {\n const socket = this._webSocket;\n\n if (socket) {\n return socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING;\n }\n return false;\n }\n\n get(url) {\n return this.ajax({\n type: 'GET',\n url\n });\n }\n\n getJSON(url, includeAuthorization) {\n return this.fetch(\n {\n url,\n type: 'GET',\n dataType: 'json',\n headers: {\n accept: 'application/json'\n }\n },\n includeAuthorization\n );\n }\n\n updateServerInfo(server, serverUrl) {\n if (server == null) {\n throw new Error('server cannot be null');\n }\n\n this.serverInfo(server);\n\n if (!serverUrl) {\n throw new Error(`serverUrl cannot be null. serverInfo: ${JSON.stringify(server)}`);\n }\n console.log(`Setting server address to ${serverUrl}`);\n this.serverAddress(serverUrl);\n }\n\n isWebSocketSupported() {\n try {\n return WebSocket != null;\n } catch (err) {\n return false;\n }\n }\n\n clearAuthenticationInfo() {\n this.setAuthenticationInfo(null, null);\n }\n\n encodeName(name) {\n name = name.split('/').join('-');\n name = name.split('&').join('-');\n name = name.split('?').join('-');\n\n const val = paramsToString({ name });\n return val.substring(val.indexOf('=') + 1).replace(\"'\", '%27');\n }\n\n /**\n * Gets the server time as a UTC formatted string.\n * @returns {Promise} Promise that it's fulfilled on request completion.\n * @since 10.6.0\n */\n getServerTime() {\n const url = this.getUrl('GetUTCTime');\n\n return this.ajax({\n type: 'GET',\n url: url\n });\n }\n\n getDownloadSpeed(byteSize) {\n const url = this.getUrl('Playback/BitrateTest', {\n Size: byteSize\n });\n\n const now = new Date().getTime();\n\n return this.ajax({\n type: 'GET',\n url,\n timeout: 5000\n }).then(() => {\n const responseTimeSeconds = (new Date().getTime() - now) / 1000;\n const bytesPerSecond = byteSize / responseTimeSeconds;\n const bitrate = Math.round(bytesPerSecond * 8);\n\n return bitrate;\n });\n }\n\n detectBitrate(force) {\n if (\n !force &&\n this.lastDetectedBitrate &&\n new Date().getTime() - (this.lastDetectedBitrateTime || 0) <= 3600000\n ) {\n return Promise.resolve(this.lastDetectedBitrate);\n }\n\n const instance = this;\n\n return this.getEndpointInfo().then(\n (info) => detectBitrateWithEndpointInfo(instance, info),\n (info) => detectBitrateWithEndpointInfo(instance, {})\n );\n }\n\n /**\n * Gets an item from the server\n * Omit itemId to get the root folder.\n */\n getItem(userId, itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = userId ? this.getUrl(`Users/${userId}/Items/${itemId}`) : this.getUrl(`Items/${itemId}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the root folder from the server\n */\n getRootFolder(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/Root`);\n\n return this.getJSON(url);\n }\n\n getNotificationSummary(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Notifications/${userId}/Summary`);\n\n return this.getJSON(url);\n }\n\n getNotifications(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Notifications/${userId}`, options || {});\n\n return this.getJSON(url);\n }\n\n markNotificationsRead(userId, idList, isRead) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!idList) {\n throw new Error('null idList');\n }\n\n const suffix = isRead ? 'Read' : 'Unread';\n\n const params = {\n UserId: userId,\n Ids: idList.join(',')\n };\n\n const url = this.getUrl(`Notifications/${userId}/${suffix}`, params);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getRemoteImageProviders(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const urlPrefix = getRemoteImagePrefix(this, options);\n\n const url = this.getUrl(`${urlPrefix}/RemoteImages/Providers`, options);\n\n return this.getJSON(url);\n }\n\n getAvailableRemoteImages(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const urlPrefix = getRemoteImagePrefix(this, options);\n\n const url = this.getUrl(`${urlPrefix}/RemoteImages`, options);\n\n return this.getJSON(url);\n }\n\n downloadRemoteImage(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const urlPrefix = getRemoteImagePrefix(this, options);\n\n const url = this.getUrl(`${urlPrefix}/RemoteImages/Download`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getRecordingFolders(userId) {\n const url = this.getUrl('LiveTv/Recordings/Folders', { userId: userId });\n\n return this.getJSON(url);\n }\n\n getLiveTvInfo(options) {\n const url = this.getUrl('LiveTv/Info', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvGuideInfo(options) {\n const url = this.getUrl('LiveTv/GuideInfo', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvChannel(id, userId) {\n if (!id) {\n throw new Error('null id');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`LiveTv/Channels/${id}`, options);\n\n return this.getJSON(url);\n }\n\n getLiveTvChannels(options) {\n const url = this.getUrl('LiveTv/Channels', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvPrograms(options = {}) {\n if (options.channelIds && options.channelIds.length > 1800) {\n return this.ajax({\n type: 'POST',\n url: this.getUrl('LiveTv/Programs'),\n data: JSON.stringify(options),\n contentType: 'application/json',\n dataType: 'json'\n });\n } else {\n return this.ajax({\n type: 'GET',\n url: this.getUrl('LiveTv/Programs', options),\n dataType: 'json'\n });\n }\n }\n\n getLiveTvRecommendedPrograms(options = {}) {\n return this.ajax({\n type: 'GET',\n url: this.getUrl('LiveTv/Programs/Recommended', options),\n dataType: 'json'\n });\n }\n\n getLiveTvRecordings(options) {\n const url = this.getUrl('LiveTv/Recordings', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvRecordingSeries(options) {\n const url = this.getUrl('LiveTv/Recordings/Series', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvRecordingGroups(options) {\n const url = this.getUrl('LiveTv/Recordings/Groups', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvRecordingGroup(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Recordings/Groups/${id}`);\n\n return this.getJSON(url);\n }\n\n getLiveTvRecording(id, userId) {\n if (!id) {\n throw new Error('null id');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`LiveTv/Recordings/${id}`, options);\n\n return this.getJSON(url);\n }\n\n getLiveTvProgram(id, userId) {\n if (!id) {\n throw new Error('null id');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`LiveTv/Programs/${id}`, options);\n\n return this.getJSON(url);\n }\n\n deleteLiveTvRecording(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Recordings/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n cancelLiveTvTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Timers/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n getLiveTvTimers(options) {\n const url = this.getUrl('LiveTv/Timers', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Timers/${id}`);\n\n return this.getJSON(url);\n }\n\n getNewLiveTvTimerDefaults(options = {}) {\n const url = this.getUrl('LiveTv/Timers/Defaults', options);\n\n return this.getJSON(url);\n }\n\n createLiveTvTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl('LiveTv/Timers');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n updateLiveTvTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl(`LiveTv/Timers/${item.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n resetLiveTvTuner(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Tuners/${id}/Reset`);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getLiveTvSeriesTimers(options) {\n const url = this.getUrl('LiveTv/SeriesTimers', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvSeriesTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/SeriesTimers/${id}`);\n\n return this.getJSON(url);\n }\n\n cancelLiveTvSeriesTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/SeriesTimers/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n createLiveTvSeriesTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl('LiveTv/SeriesTimers');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n updateLiveTvSeriesTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl(`LiveTv/SeriesTimers/${item.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n getRegistrationInfo(feature) {\n const url = this.getUrl(`Registrations/${feature}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server status\n */\n getSystemInfo(itemId) {\n const url = this.getUrl('System/Info');\n\n const instance = this;\n\n return this.getJSON(url).then((info) => {\n instance.setSystemInfo(info);\n return Promise.resolve(info);\n });\n }\n\n getSyncStatus() {\n const url = this.getUrl('Sync/' + itemId + '/Status');\n\n return this.ajax({\n url: url,\n type: 'POST',\n dataType: 'json',\n contentType: 'application/json',\n data: JSON.stringify({\n TargetId: this.deviceId()\n })\n });\n }\n\n /**\n * Gets the current server status\n */\n getPublicSystemInfo() {\n const url = this.getUrl('System/Info/Public');\n\n const instance = this;\n\n return this.getJSON(url).then((info) => {\n instance.setSystemInfo(info);\n return Promise.resolve(info);\n });\n }\n\n getInstantMixFromItem(itemId, options) {\n const url = this.getUrl(`Items/${itemId}/InstantMix`, options);\n\n return this.getJSON(url);\n }\n\n getEpisodes(itemId, options) {\n const url = this.getUrl(`Shows/${itemId}/Episodes`, options);\n\n return this.getJSON(url);\n }\n\n getDisplayPreferences(id, userId, app) {\n const url = this.getUrl(`DisplayPreferences/${id}`, {\n userId,\n client: app\n });\n\n return this.getJSON(url);\n }\n\n updateDisplayPreferences(id, obj, userId, app) {\n const url = this.getUrl(`DisplayPreferences/${id}`, {\n userId,\n client: app\n });\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(obj),\n contentType: 'application/json'\n });\n }\n\n getSeasons(itemId, options) {\n const url = this.getUrl(`Shows/${itemId}/Seasons`, options);\n\n return this.getJSON(url);\n }\n\n getSimilarItems(itemId, options) {\n const url = this.getUrl(`Items/${itemId}/Similar`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all cultures known to the server\n */\n getCultures() {\n const url = this.getUrl('Localization/cultures');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all countries known to the server\n */\n getCountries() {\n const url = this.getUrl('Localization/countries');\n\n return this.getJSON(url);\n }\n\n getPlaybackInfo(itemId, options, deviceProfile) {\n const postData = {\n DeviceProfile: deviceProfile\n };\n\n return this.ajax({\n url: this.getUrl(`Items/${itemId}/PlaybackInfo`, options),\n type: 'POST',\n data: JSON.stringify(postData),\n contentType: 'application/json',\n dataType: 'json'\n });\n }\n\n getLiveStreamMediaInfo(liveStreamId) {\n const postData = {\n LiveStreamId: liveStreamId\n };\n\n return this.ajax({\n url: this.getUrl('LiveStreams/MediaInfo'),\n type: 'POST',\n data: JSON.stringify(postData),\n contentType: 'application/json',\n dataType: 'json'\n });\n }\n\n getIntros(itemId) {\n return this.getJSON(this.getUrl(`Users/${this.getCurrentUserId()}/Items/${itemId}/Intros`));\n }\n\n /**\n * Gets the directory contents of a path on the server\n */\n getDirectoryContents(path, options) {\n if (!path) {\n throw new Error('null path');\n }\n if (typeof path !== 'string') {\n throw new Error('invalid path');\n }\n\n options = options || {};\n\n options.path = path;\n\n const url = this.getUrl('Environment/DirectoryContents', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets shares from a network device\n */\n getNetworkShares(path) {\n if (!path) {\n throw new Error('null path');\n }\n\n const options = {};\n options.path = path;\n\n const url = this.getUrl('Environment/NetworkShares', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the parent of a given path\n */\n getParentPath(path) {\n if (!path) {\n throw new Error('null path');\n }\n\n const options = {};\n options.path = path;\n\n const url = this.getUrl('Environment/ParentPath', options);\n\n return this.ajax({\n type: 'GET',\n url,\n dataType: 'text'\n });\n }\n\n /**\n * Gets a list of physical drives from the server\n */\n getDrives() {\n const url = this.getUrl('Environment/Drives');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a list of network devices from the server\n */\n getNetworkDevices() {\n const url = this.getUrl('Environment/NetworkDevices');\n\n return this.getJSON(url);\n }\n\n /**\n * Cancels a package installation\n */\n cancelPackageInstallation(installationId) {\n if (!installationId) {\n throw new Error('null installationId');\n }\n\n const url = this.getUrl(`Packages/Installing/${installationId}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Refreshes metadata for an item\n */\n refreshItem(itemId, options) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Items/${itemId}/Refresh`, options || {});\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Installs or updates a new plugin\n */\n installPlugin(name, guid, version) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {\n AssemblyGuid: guid\n };\n\n if (version) {\n options.version = version;\n }\n\n const url = this.getUrl(`Packages/Installed/${name}`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Instructs the server to perform a restart.\n */\n restartServer() {\n const url = this.getUrl('System/Restart');\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Instructs the server to perform a shutdown.\n */\n shutdownServer() {\n const url = this.getUrl('System/Shutdown');\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Gets information about an installable package\n */\n getPackageInfo(name, guid) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {\n AssemblyGuid: guid\n };\n\n const url = this.getUrl(`Packages/${name}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the virtual folder list\n */\n getVirtualFolders() {\n let url = 'Library/VirtualFolders';\n\n url = this.getUrl(url);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all the paths of the locations in the physical root.\n */\n getPhysicalPaths() {\n const url = this.getUrl('Library/PhysicalPaths');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server configuration\n */\n getServerConfiguration() {\n const url = this.getUrl('System/Configuration');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server configuration\n */\n getDevicesOptions() {\n const url = this.getUrl('System/Configuration/devices');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server configuration\n */\n getContentUploadHistory() {\n const url = this.getUrl('Devices/CameraUploads', {\n DeviceId: this.deviceId()\n });\n\n return this.getJSON(url);\n }\n\n getNamedConfiguration(name) {\n const url = this.getUrl(`System/Configuration/${name}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the server's scheduled tasks\n */\n getScheduledTasks(options = {}) {\n const url = this.getUrl('ScheduledTasks', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Starts a scheduled task\n */\n startScheduledTask(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`ScheduledTasks/Running/${id}`);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Gets a scheduled task\n */\n getScheduledTask(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`ScheduledTasks/${id}`);\n\n return this.getJSON(url);\n }\n\n getNextUpEpisodes(options) {\n const url = this.getUrl('Shows/NextUp', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Stops a scheduled task\n */\n stopScheduledTask(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`ScheduledTasks/Running/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Gets the configuration of a plugin\n * @param {String} Id\n */\n getPluginConfiguration(id) {\n if (!id) {\n throw new Error('null Id');\n }\n\n const url = this.getUrl(`Plugins/${id}/Configuration`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a list of plugins that are available to be installed\n */\n getAvailablePlugins(options = {}) {\n options.PackageType = 'UserInstalled';\n\n const url = this.getUrl('Packages', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Uninstalls a plugin\n * @param {String} Id\n */\n uninstallPlugin(id) {\n if (!id) {\n throw new Error('null Id');\n }\n\n const url = this.getUrl(`Plugins/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Removes a virtual folder\n * @param {String} name\n */\n removeVirtualFolder(name, refreshLibrary) {\n if (!name) {\n throw new Error('null name');\n }\n\n let url = 'Library/VirtualFolders';\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false,\n name\n });\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Adds a virtual folder\n * @param {String} name\n */\n addVirtualFolder(name, type, refreshLibrary, libraryOptions) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (type) {\n options.collectionType = type;\n }\n\n options.refreshLibrary = refreshLibrary ? true : false;\n options.name = name;\n\n let url = 'Library/VirtualFolders';\n\n url = this.getUrl(url, options);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n LibraryOptions: libraryOptions\n }),\n contentType: 'application/json'\n });\n }\n\n updateVirtualFolderOptions(id, libraryOptions) {\n if (!id) {\n throw new Error('null name');\n }\n\n let url = 'Library/VirtualFolders/LibraryOptions';\n\n url = this.getUrl(url);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n Id: id,\n LibraryOptions: libraryOptions\n }),\n contentType: 'application/json'\n });\n }\n\n /**\n * Renames a virtual folder\n * @param {String} name\n */\n renameVirtualFolder(name, newName, refreshLibrary) {\n if (!name) {\n throw new Error('null name');\n }\n\n let url = 'Library/VirtualFolders/Name';\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false,\n newName,\n name\n });\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Adds an additional mediaPath to an existing virtual folder\n * @param {String} name\n */\n addMediaPath(virtualFolderName, mediaPath, networkSharePath, refreshLibrary) {\n if (!virtualFolderName) {\n throw new Error('null virtualFolderName');\n }\n\n if (!mediaPath) {\n throw new Error('null mediaPath');\n }\n\n let url = 'Library/VirtualFolders/Paths';\n\n const pathInfo = {\n Path: mediaPath\n };\n if (networkSharePath) {\n pathInfo.NetworkPath = networkSharePath;\n }\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false\n });\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n Name: virtualFolderName,\n PathInfo: pathInfo\n }),\n contentType: 'application/json'\n });\n }\n\n updateMediaPath(virtualFolderName, pathInfo) {\n if (!virtualFolderName) {\n throw new Error('null virtualFolderName');\n }\n\n if (!pathInfo) {\n throw new Error('null pathInfo');\n }\n\n let url = 'Library/VirtualFolders/Paths/Update';\n\n url = this.getUrl(url);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n Name: virtualFolderName,\n PathInfo: pathInfo\n }),\n contentType: 'application/json'\n });\n }\n\n /**\n * Removes a media path from a virtual folder\n * @param {String} name\n */\n removeMediaPath(virtualFolderName, mediaPath, refreshLibrary) {\n if (!virtualFolderName) {\n throw new Error('null virtualFolderName');\n }\n\n if (!mediaPath) {\n throw new Error('null mediaPath');\n }\n\n let url = 'Library/VirtualFolders/Paths';\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false,\n path: mediaPath,\n name: virtualFolderName\n });\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Deletes a user\n * @param {String} id\n */\n deleteUser(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`Users/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Deletes a user image\n * @param {String} userId\n * @param {String} imageType The type of image to delete, based on the server-side ImageType enum.\n */\n deleteUserImage(userId, imageType, imageIndex) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n let url = this.getUrl(`Users/${userId}/Images/${imageType}`);\n\n if (imageIndex != null) {\n url += `/${imageIndex}`;\n }\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n deleteItemImage(itemId, imageType, imageIndex) {\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n let url = this.getUrl(`Items/${itemId}/Images`);\n\n url += `/${imageType}`;\n\n if (imageIndex != null) {\n url += `/${imageIndex}`;\n }\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n deleteItem(itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Items/${itemId}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n stopActiveEncodings(playSessionId) {\n const options = {\n deviceId: this.deviceId()\n };\n\n if (playSessionId) {\n options.PlaySessionId = playSessionId;\n }\n\n const url = this.getUrl('Videos/ActiveEncodings', options);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n reportCapabilities(options) {\n const url = this.getUrl('Sessions/Capabilities/Full');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(options),\n contentType: 'application/json'\n });\n }\n\n updateItemImageIndex(itemId, imageType, imageIndex, newIndex) {\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n const options = { newIndex };\n\n const url = this.getUrl(`Items/${itemId}/Images/${imageType}/${imageIndex}/Index`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getItemImageInfos(itemId) {\n const url = this.getUrl(`Items/${itemId}/Images`);\n\n return this.getJSON(url);\n }\n\n getCriticReviews(itemId, options) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Items/${itemId}/CriticReviews`, options);\n\n return this.getJSON(url);\n }\n\n getItemDownloadUrl(itemId) {\n if (!itemId) {\n throw new Error('itemId cannot be empty');\n }\n\n const url = `Items/${itemId}/Download`;\n\n return this.getUrl(url, {\n api_key: this.accessToken()\n });\n }\n\n getSessions(options) {\n const url = this.getUrl('Sessions', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Uploads a user image\n * @param {String} userId\n * @param {String} imageType The type of image to delete, based on the server-side ImageType enum.\n * @param {Object} file The file from the input element\n */\n uploadUserImage(userId, imageType, file) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n if (!file) {\n throw new Error('File must be an image.');\n }\n\n if (!file.type.startsWith('image/')) {\n throw new Error('File must be an image.');\n }\n\n const instance = this;\n\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n\n reader.onerror = () => {\n reject();\n };\n\n reader.onabort = () => {\n reject();\n };\n\n // Closure to capture the file information.\n reader.onload = (e) => {\n // Split by a comma to remove the url: prefix\n const data = e.target.result.split(',')[1];\n\n const url = instance.getUrl(`Users/${userId}/Images/${imageType}`);\n\n instance\n .ajax({\n type: 'POST',\n url,\n data,\n contentType: `image/${file.name.substring(file.name.lastIndexOf('.') + 1)}`\n })\n .then(resolve, reject);\n };\n\n // Read in the image file as a data URL.\n reader.readAsDataURL(file);\n });\n }\n\n uploadItemImage(itemId, imageType, file) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n if (!file) {\n throw new Error('File must be an image.');\n }\n\n if (!file.type.startsWith('image/')) {\n throw new Error('File must be an image.');\n }\n\n let url = this.getUrl(`Items/${itemId}/Images`);\n\n url += `/${imageType}`;\n const instance = this;\n\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n\n reader.onerror = () => {\n reject();\n };\n\n reader.onabort = () => {\n reject();\n };\n\n // Closure to capture the file information.\n reader.onload = (e) => {\n // Split by a comma to remove the url: prefix\n const data = e.target.result.split(',')[1];\n\n instance\n .ajax({\n type: 'POST',\n url,\n data,\n contentType: `image/${file.name.substring(file.name.lastIndexOf('.') + 1)}`\n })\n .then(resolve, reject);\n };\n\n // Read in the image file as a data URL.\n reader.readAsDataURL(file);\n });\n }\n\n /**\n * Gets the list of installed plugins on the server\n */\n getInstalledPlugins() {\n const options = {};\n\n const url = this.getUrl('Plugins', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a user by id\n * @param {String} id\n */\n getUser(id) {\n if (!id) {\n throw new Error('Must supply a userId');\n }\n\n const url = this.getUrl(`Users/${id}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a studio\n */\n getStudio(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Studios/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a genre\n */\n getGenre(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Genres/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n getMusicGenre(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`MusicGenres/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets an artist\n */\n getArtist(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Artists/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a Person\n */\n getPerson(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Persons/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n getPublicUsers() {\n const url = this.getUrl('users/public');\n\n return this.ajax(\n {\n type: 'GET',\n url,\n dataType: 'json'\n },\n false\n );\n }\n\n /**\n * Gets all users from the server\n */\n getUsers(options) {\n const url = this.getUrl('users', options || {});\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all available parental ratings from the server\n */\n getParentalRatings() {\n const url = this.getUrl('Localization/ParentalRatings');\n\n return this.getJSON(url);\n }\n\n getDefaultImageQuality(imageType) {\n return imageType.toLowerCase() === 'backdrop' ? 80 : 90;\n }\n\n /**\n * Constructs a url for a user image\n * @param {String} userId\n * @param {Object} options\n * Options supports the following properties:\n * width - download the image at a fixed width\n * height - download the image at a fixed height\n * maxWidth - download the image at a maxWidth\n * maxHeight - download the image at a maxHeight\n * quality - A scale of 0-100. This should almost always be omitted as the default will suffice.\n * For best results do not specify both width and height together, as aspect ratio might be altered.\n */\n getUserImageUrl(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n\n let url = `Users/${userId}/Images/${options.type}`;\n\n if (options.index != null) {\n url += `/${options.index}`;\n }\n\n normalizeImageOptions(this, options);\n\n // Don't put these on the query string\n delete options.type;\n delete options.index;\n\n return this.getUrl(url, options);\n }\n\n /**\n * Constructs a url for an item image\n * @param {String} itemId\n * @param {Object} options\n * Options supports the following properties:\n * type - Primary, logo, backdrop, etc. See the server-side enum ImageType\n * index - When downloading a backdrop, use this to specify which one (omitting is equivalent to zero)\n * width - download the image at a fixed width\n * height - download the image at a fixed height\n * maxWidth - download the image at a maxWidth\n * maxHeight - download the image at a maxHeight\n * quality - A scale of 0-100. This should almost always be omitted as the default will suffice.\n * For best results do not specify both width and height together, as aspect ratio might be altered.\n */\n getImageUrl(itemId, options) {\n if (!itemId) {\n throw new Error('itemId cannot be empty');\n }\n\n options = options || {};\n\n let url = `Items/${itemId}/Images/${options.type}`;\n\n if (options.index != null) {\n url += `/${options.index}`;\n }\n\n options.quality = options.quality || this.getDefaultImageQuality(options.type);\n\n if (this.normalizeImageOptions) {\n this.normalizeImageOptions(options);\n }\n\n // Don't put these on the query string\n delete options.type;\n delete options.index;\n\n return this.getUrl(url, options);\n }\n\n getScaledImageUrl(itemId, options) {\n if (!itemId) {\n throw new Error('itemId cannot be empty');\n }\n\n options = options || {};\n\n let url = `Items/${itemId}/Images/${options.type}`;\n\n if (options.index != null) {\n url += `/${options.index}`;\n }\n\n normalizeImageOptions(this, options);\n\n // Don't put these on the query string\n delete options.type;\n delete options.index;\n delete options.minScale;\n\n return this.getUrl(url, options);\n }\n\n getThumbImageUrl(item, options) {\n if (!item) {\n throw new Error('null item');\n }\n\n options = options || {};\n\n options.imageType = 'thumb';\n\n if (item.ImageTags && item.ImageTags.Thumb) {\n options.tag = item.ImageTags.Thumb;\n return this.getImageUrl(item.Id, options);\n } else if (item.ParentThumbItemId) {\n options.tag = item.ImageTags.ParentThumbImageTag;\n return this.getImageUrl(item.ParentThumbItemId, options);\n } else {\n return null;\n }\n }\n\n /**\n * Updates a user's password\n * @param {String} userId\n * @param {String} currentPassword\n * @param {String} newPassword\n */\n updateUserPassword(userId, currentPassword, newPassword) {\n if (!userId) {\n return Promise.reject();\n }\n\n const url = this.getUrl(`Users/${userId}/Password`);\n\n return this.ajax({\n type: 'POST',\n url: url,\n data: JSON.stringify({\n CurrentPw: currentPassword || '',\n NewPw: newPassword\n }),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates a user's easy password\n * @param {String} userId\n * @param {String} newPassword\n */\n updateEasyPassword(userId, newPassword) {\n if (!userId) {\n Promise.reject();\n return;\n }\n\n const url = this.getUrl(`Users/${userId}/EasyPassword`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: {\n NewPw: newPassword\n }\n });\n }\n\n /**\n * Resets a user's password\n * @param {String} userId\n */\n resetUserPassword(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Users/${userId}/Password`);\n\n const postData = {};\n\n postData.resetPassword = true;\n\n return this.ajax({\n type: 'POST',\n url,\n data: postData\n });\n }\n\n resetEasyPassword(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Users/${userId}/EasyPassword`);\n\n const postData = {};\n\n postData.resetPassword = true;\n\n return this.ajax({\n type: 'POST',\n url,\n data: postData\n });\n }\n\n /**\n * Updates the server's configuration\n * @param {Object} configuration\n */\n updateServerConfiguration(configuration) {\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl('System/Configuration');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n updateNamedConfiguration(name, configuration) {\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl(`System/Configuration/${name}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n updateItem(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl(`Items/${item.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates plugin security info\n */\n updatePluginSecurityInfo(info) {\n const url = this.getUrl('Plugins/SecurityInfo');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(info),\n contentType: 'application/json'\n });\n }\n\n /**\n * Creates a user\n * @param {Object} user\n */\n createUser(user) {\n const url = this.getUrl('Users/New');\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(user),\n contentType: 'application/json',\n headers: {\n accept: 'application/json'\n }\n });\n }\n\n /**\n * Updates a user\n * @param {Object} user\n */\n updateUser(user) {\n if (!user) {\n throw new Error('null user');\n }\n\n const url = this.getUrl(`Users/${user.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(user),\n contentType: 'application/json'\n });\n }\n\n updateUserPolicy(userId, policy) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!policy) {\n throw new Error('null policy');\n }\n\n const url = this.getUrl(`Users/${userId}/Policy`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(policy),\n contentType: 'application/json'\n });\n }\n\n updateUserConfiguration(userId, configuration) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl(`Users/${userId}/Configuration`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates the Triggers for a ScheduledTask\n * @param {String} id\n * @param {Object} triggers\n */\n updateScheduledTaskTriggers(id, triggers) {\n if (!id) {\n throw new Error('null id');\n }\n\n if (!triggers) {\n throw new Error('null triggers');\n }\n\n const url = this.getUrl(`ScheduledTasks/${id}/Triggers`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(triggers),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates a plugin's configuration\n * @param {String} Id\n * @param {Object} configuration\n */\n updatePluginConfiguration(id, configuration) {\n if (!id) {\n throw new Error('null Id');\n }\n\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl(`Plugins/${id}/Configuration`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n getAncestorItems(itemId, userId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Items/${itemId}/Ancestors`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets items based on a query, typically for children of a folder\n * @param {String} userId\n * @param {Object} options\n * Options accepts the following properties:\n * itemId - Localize the search to a specific folder (root if omitted)\n * startIndex - Use for paging\n * limit - Use to limit results to a certain number of items\n * filter - Specify one or more ItemFilters, comma delimeted (see server-side enum)\n * sortBy - Specify an ItemSortBy (comma-delimeted list see server-side enum)\n * sortOrder - ascending/descending\n * fields - additional fields to include aside from basic info. This is a comma delimited list. See server-side enum ItemFields.\n * index - the name of the dynamic, localized index function\n * dynamicSortBy - the name of the dynamic localized sort function\n * recursive - Whether or not the query should be recursive\n * searchTerm - search term to use as a filter\n */\n getItems(userId, options) {\n let url;\n\n if ((typeof userId).toString().toLowerCase() === 'string') {\n url = this.getUrl(`Users/${userId}/Items`, options);\n } else {\n url = this.getUrl('Items', options);\n }\n\n return this.getJSON(url);\n }\n\n getResumableItems(userId, options) {\n if (this.isMinServerVersion('3.2.33')) {\n return this.getJSON(this.getUrl(`Users/${userId}/Items/Resume`, options));\n }\n\n return this.getItems(\n userId,\n Object.assign(\n {\n SortBy: 'DatePlayed',\n SortOrder: 'Descending',\n Filters: 'IsResumable',\n Recursive: true,\n CollapseBoxSetItems: false,\n ExcludeLocationTypes: 'Virtual'\n },\n options\n )\n );\n }\n\n getMovieRecommendations(options) {\n return this.getJSON(this.getUrl('Movies/Recommendations', options));\n }\n\n getUpcomingEpisodes(options) {\n return this.getJSON(this.getUrl('Shows/Upcoming', options));\n }\n\n getUserViews(options = {}, userId) {\n const url = this.getUrl(`Users/${userId || this.getCurrentUserId()}/Views`, options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets artists from an item\n */\n getArtists(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Artists', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets artists from an item\n */\n getAlbumArtists(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Artists/AlbumArtists', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets genres from an item\n */\n getGenres(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Genres', options);\n\n return this.getJSON(url);\n }\n\n getMusicGenres(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('MusicGenres', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets people from an item\n */\n getPeople(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Persons', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets studios from an item\n */\n getStudios(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Studios', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets local trailers for an item\n */\n getLocalTrailers(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/LocalTrailers`);\n\n return this.getJSON(url);\n }\n\n getAdditionalVideoParts(userId, itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Videos/${itemId}/AdditionalParts`, options);\n\n return this.getJSON(url);\n }\n\n getThemeMedia(userId, itemId, inherit) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n options.InheritFromParent = inherit || false;\n\n const url = this.getUrl(`Items/${itemId}/ThemeMedia`, options);\n\n return this.getJSON(url);\n }\n\n getSearchHints(options) {\n const url = this.getUrl('Search/Hints', options);\n const serverId = this.serverId();\n\n return this.getJSON(url).then((result) => {\n result.SearchHints.forEach((i) => {\n i.ServerId = serverId;\n });\n return result;\n });\n }\n\n /**\n * Gets special features for an item\n */\n getSpecialFeatures(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/SpecialFeatures`);\n\n return this.getJSON(url);\n }\n\n getDateParamValue(date) {\n function formatDigit(i) {\n return i < 10 ? `0${i}` : i;\n }\n\n const d = date;\n\n return `${d.getFullYear()}${formatDigit(d.getMonth() + 1)}${formatDigit(d.getDate())}${formatDigit(\n d.getHours()\n )}${formatDigit(d.getMinutes())}${formatDigit(d.getSeconds())}`;\n }\n\n markPlayed(userId, itemId, date) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (date) {\n options.DatePlayed = this.getDateParamValue(date);\n }\n\n const url = this.getUrl(`Users/${userId}/PlayedItems/${itemId}`, options);\n\n return this.ajax({\n type: 'POST',\n url,\n dataType: 'json'\n });\n }\n\n markUnplayed(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/PlayedItems/${itemId}`);\n\n return this.ajax({\n type: 'DELETE',\n url,\n dataType: 'json'\n });\n }\n\n /**\n * Updates a user's favorite status for an item.\n * @param {String} userId\n * @param {String} itemId\n * @param {Boolean} isFavorite\n */\n updateFavoriteStatus(userId, itemId, isFavorite) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/FavoriteItems/${itemId}`);\n\n const method = isFavorite ? 'POST' : 'DELETE';\n\n return this.ajax({\n type: method,\n url,\n dataType: 'json'\n });\n }\n\n /**\n * Updates a user's personal rating for an item\n * @param {String} userId\n * @param {String} itemId\n * @param {Boolean} likes\n */\n updateUserItemRating(userId, itemId, likes) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/Rating`, {\n likes\n });\n\n return this.ajax({\n type: 'POST',\n url,\n dataType: 'json'\n });\n }\n\n getItemCounts(userId) {\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl('Items/Counts', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Clears a user's personal rating for an item\n * @param {String} userId\n * @param {String} itemId\n */\n clearUserItemRating(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/Rating`);\n\n return this.ajax({\n type: 'DELETE',\n url,\n dataType: 'json'\n });\n }\n\n /**\n * Reports the user has started playing something\n * @param {String} userId\n * @param {String} itemId\n */\n reportPlaybackStart(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n this.lastPlaybackProgressReport = 0;\n this.lastPlaybackProgressReportTicks = null;\n stopBitrateDetection(this);\n\n cancelReportPlaybackProgressPromise(this);\n const url = this.getUrl('Sessions/Playing');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(options),\n contentType: 'application/json',\n url\n });\n }\n\n /**\n * Reports progress viewing an item\n * @param {String} userId\n * @param {String} itemId\n */\n reportPlaybackProgress(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const eventName = options.EventName || 'timeupdate';\n let reportRateLimitTime = reportRateLimits[eventName] || 0;\n\n const now = new Date().getTime();\n const msSinceLastReport = now - (this.lastPlaybackProgressReport || 0);\n const newPositionTicks = options.PositionTicks;\n\n if (msSinceLastReport < reportRateLimitTime && eventName === 'timeupdate' && newPositionTicks) {\n const expectedReportTicks = 1e4 * msSinceLastReport + (this.lastPlaybackProgressReportTicks || 0);\n if (Math.abs(newPositionTicks - expectedReportTicks) >= 5e7) reportRateLimitTime = 0;\n }\n\n if (\n reportRateLimitTime <\n (this.reportPlaybackProgressTimeout !== undefined ? this.reportPlaybackProgressTimeout : 1e6)\n ) {\n cancelReportPlaybackProgressPromise(this);\n }\n\n this.lastPlaybackProgressOptions = options;\n\n /* eslint-disable-next-line @typescript-eslint/no-misused-promises */\n if (this.reportPlaybackProgressPromise) return Promise.resolve();\n\n let instance = this;\n let promise;\n let cancelled = false;\n\n let resetPromise = function () {\n if (instance.reportPlaybackProgressPromise !== promise) return;\n\n delete instance.lastPlaybackProgressOptions;\n delete instance.reportPlaybackProgressTimeout;\n delete instance.reportPlaybackProgressPromise;\n delete instance.reportPlaybackProgressCancel;\n };\n\n let sendReport = function (lastOptions) {\n resetPromise();\n\n if (!lastOptions) throw new Error('null options');\n\n instance.lastPlaybackProgressReport = new Date().getTime();\n instance.lastPlaybackProgressReportTicks = lastOptions.PositionTicks;\n\n const url = instance.getUrl('Sessions/Playing/Progress');\n return instance.ajax({\n type: 'POST',\n data: JSON.stringify(lastOptions),\n contentType: 'application/json',\n url: url\n });\n };\n\n let delay = Math.max(0, reportRateLimitTime - msSinceLastReport);\n\n promise = new Promise((resolve, reject) => setTimeout(resolve, delay))\n .then(() => {\n if (cancelled) return Promise.resolve();\n return sendReport(instance.lastPlaybackProgressOptions);\n })\n .finally(() => {\n resetPromise();\n });\n\n this.reportPlaybackProgressTimeout = reportRateLimitTime;\n this.reportPlaybackProgressPromise = promise;\n this.reportPlaybackProgressCancel = function () {\n cancelled = true;\n resetPromise();\n };\n\n return promise;\n }\n\n reportOfflineActions(actions) {\n if (!actions) {\n throw new Error('null actions');\n }\n\n const url = this.getUrl('Sync/OfflineActions');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(actions),\n contentType: 'application/json',\n url\n });\n }\n\n syncData(data) {\n if (!data) {\n throw new Error('null data');\n }\n\n const url = this.getUrl('Sync/Data');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(data),\n contentType: 'application/json',\n url,\n dataType: 'json'\n });\n }\n\n getReadySyncItems(deviceId) {\n if (!deviceId) {\n throw new Error('null deviceId');\n }\n\n const url = this.getUrl('Sync/Items/Ready', {\n TargetId: deviceId\n });\n\n return this.getJSON(url);\n }\n\n reportSyncJobItemTransferred(syncJobItemId) {\n if (!syncJobItemId) {\n throw new Error('null syncJobItemId');\n }\n\n const url = this.getUrl(`Sync/JobItems/${syncJobItemId}/Transferred`);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n cancelSyncItems(itemIds, targetId) {\n if (!itemIds) {\n throw new Error('null itemIds');\n }\n\n const url = this.getUrl(`Sync/${targetId || this.deviceId()}/Items`, {\n ItemIds: itemIds.join(',')\n });\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Reports a user has stopped playing an item\n * @param {String} userId\n * @param {String} itemId\n */\n reportPlaybackStopped(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n this.lastPlaybackProgressReport = 0;\n this.lastPlaybackProgressReportTicks = null;\n redetectBitrate(this);\n\n cancelReportPlaybackProgressPromise(this);\n const url = this.getUrl('Sessions/Playing/Stopped');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(options),\n contentType: 'application/json',\n url\n });\n }\n\n sendPlayCommand(sessionId, options) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!options) {\n throw new Error('null options');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Playing`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n sendCommand(sessionId, command) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!command) {\n throw new Error('null command');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Command`);\n\n const ajaxOptions = {\n type: 'POST',\n url\n };\n\n ajaxOptions.data = JSON.stringify(command);\n ajaxOptions.contentType = 'application/json';\n\n return this.ajax(ajaxOptions);\n }\n\n sendMessageCommand(sessionId, options) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!options) {\n throw new Error('null options');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Message`);\n\n const ajaxOptions = {\n type: 'POST',\n url\n };\n\n ajaxOptions.data = JSON.stringify(options);\n ajaxOptions.contentType = 'application/json';\n\n return this.ajax(ajaxOptions);\n }\n\n sendPlayStateCommand(sessionId, command, options) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!command) {\n throw new Error('null command');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Playing/${command}`, options || {});\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Gets a list of all the active SyncPlay groups from the server.\n * @returns {Promise} A Promise that resolves to the list of active groups.\n * @since 10.6.0\n */\n getSyncPlayGroups() {\n const url = this.getUrl(`SyncPlay/List`);\n\n return this.ajax({\n type: 'GET',\n url: url\n });\n }\n\n /**\n * Creates a SyncPlay group on the server with the current client as member.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n createSyncPlayGroup() {\n const url = this.getUrl(`SyncPlay/New`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Joins the client to a given SyncPlay group on the server.\n * @param {object} options Information about the SyncPlay group to join.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n joinSyncPlayGroup(options = {}) {\n const url = this.getUrl(`SyncPlay/Join`, options);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Leaves the current SyncPlay group.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n leaveSyncPlayGroup() {\n const url = this.getUrl(`SyncPlay/Leave`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Sends a ping to the SyncPlay group on the server.\n * @param {object} options Information about the ping\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n sendSyncPlayPing(options = {}) {\n const url = this.getUrl(`SyncPlay/Ping`, options);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Requests a playback start for the SyncPlay group\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n requestSyncPlayStart() {\n const url = this.getUrl(`SyncPlay/Play`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Requests a playback pause for the SyncPlay group\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n requestSyncPlayPause() {\n const url = this.getUrl(`SyncPlay/Pause`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Requests a playback seek for the SyncPlay group\n * @param {object} options Object containing the requested seek position.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n requestSyncPlaySeek(options = {}) {\n const url = this.getUrl(`SyncPlay/Seek`, options);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n createPackageReview(review) {\n const url = this.getUrl(`Packages/Reviews/${review.id}`, review);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getPackageReviews(packageId, minRating, maxRating, limit) {\n if (!packageId) {\n throw new Error('null packageId');\n }\n\n const options = {};\n\n if (minRating) {\n options.MinRating = minRating;\n }\n if (maxRating) {\n options.MaxRating = maxRating;\n }\n if (limit) {\n options.Limit = limit;\n }\n\n const url = this.getUrl(`Packages/${packageId}/Reviews`, options);\n\n return this.getJSON(url);\n }\n\n getSavedEndpointInfo() {\n return this._endPointInfo;\n }\n\n getEndpointInfo() {\n const savedValue = this._endPointInfo;\n if (savedValue) {\n return Promise.resolve(savedValue);\n }\n\n const instance = this;\n return this.getJSON(this.getUrl('System/Endpoint')).then((endPointInfo) => {\n setSavedEndpointInfo(instance, endPointInfo);\n return endPointInfo;\n });\n }\n\n getLatestItems(options = {}) {\n return this.getJSON(this.getUrl(`Users/${this.getCurrentUserId()}/Items/Latest`, options));\n }\n\n getFilters(options) {\n return this.getJSON(this.getUrl('Items/Filters2', options));\n }\n\n setSystemInfo(info) {\n this._serverVersion = info.Version;\n }\n\n serverVersion() {\n return this._serverVersion;\n }\n\n isMinServerVersion(version) {\n const serverVersion = this.serverVersion();\n\n if (serverVersion) {\n return compareVersions(serverVersion, version) >= 0;\n }\n\n return false;\n }\n\n handleMessageReceived(msg) {\n onMessageReceivedInternal(this, msg);\n }\n}\n\nfunction setSavedEndpointInfo(instance, info) {\n instance._endPointInfo = info;\n}\n\nfunction getTryConnectPromise(instance, url, state, resolve, reject) {\n console.log('getTryConnectPromise ' + url);\n\n fetchWithTimeout(\n instance.getUrl('system/info/public', null, url),\n {\n method: 'GET',\n accept: 'application/json'\n\n // Commenting this out since the fetch api doesn't have a timeout option yet\n //timeout: timeout\n },\n 15000\n ).then(\n () => {\n if (!state.resolved) {\n state.resolved = true;\n\n console.log('Reconnect succeeded to ' + url);\n instance.serverAddress(url);\n resolve();\n }\n },\n () => {\n if (!state.resolved) {\n console.log('Reconnect failed to ' + url);\n\n state.rejects++;\n if (state.rejects >= state.numAddresses) {\n reject();\n }\n }\n }\n );\n}\n\nfunction tryReconnectInternal(instance) {\n const addresses = [];\n const addressesStrings = [];\n\n const serverInfo = instance.serverInfo();\n if (serverInfo.LocalAddress && addressesStrings.indexOf(serverInfo.LocalAddress) === -1) {\n addresses.push({ url: serverInfo.LocalAddress, timeout: 0 });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (serverInfo.ManualAddress && addressesStrings.indexOf(serverInfo.ManualAddress) === -1) {\n addresses.push({ url: serverInfo.ManualAddress, timeout: 100 });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (serverInfo.RemoteAddress && addressesStrings.indexOf(serverInfo.RemoteAddress) === -1) {\n addresses.push({ url: serverInfo.RemoteAddress, timeout: 200 });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n\n console.log('tryReconnect: ' + addressesStrings.join('|'));\n\n return new Promise((resolve, reject) => {\n const state = {};\n state.numAddresses = addresses.length;\n state.rejects = 0;\n\n addresses.map((url) => {\n setTimeout(() => {\n if (!state.resolved) {\n getTryConnectPromise(instance, url.url, state, resolve, reject);\n }\n }, url.timeout);\n });\n });\n}\n\nfunction tryReconnect(instance, retryCount) {\n retryCount = retryCount || 0;\n\n if (retryCount >= 20) {\n return Promise.reject();\n }\n\n return tryReconnectInternal(instance).catch((err) => {\n console.log('error in tryReconnectInternal: ' + (err || ''));\n\n return new Promise((resolve, reject) => {\n setTimeout(() => {\n tryReconnect(instance, retryCount + 1).then(resolve, reject);\n }, 500);\n });\n });\n}\n\nfunction getCachedUser(instance, userId) {\n const serverId = instance.serverId();\n if (!serverId) {\n return null;\n }\n\n const json = appStorage.getItem(`user-${userId}-${serverId}`);\n\n if (json) {\n return JSON.parse(json);\n }\n\n return null;\n}\n\nfunction onWebSocketMessage(msg) {\n const instance = this;\n msg = JSON.parse(msg.data);\n onMessageReceivedInternal(instance, msg);\n}\n\nconst messageIdsReceived = {};\n\nfunction onMessageReceivedInternal(instance, msg) {\n const messageId = msg.MessageId;\n if (messageId) {\n // message was already received via another protocol\n if (messageIdsReceived[messageId]) {\n return;\n }\n\n messageIdsReceived[messageId] = true;\n }\n\n if (msg.MessageType === 'UserDeleted') {\n instance._currentUser = null;\n } else if (msg.MessageType === 'UserUpdated' || msg.MessageType === 'UserConfigurationUpdated') {\n const user = msg.Data;\n if (user.Id === instance.getCurrentUserId()) {\n instance._currentUser = null;\n }\n } else if (msg.MessageType === 'KeepAlive') {\n console.debug('Received KeepAlive from server.');\n } else if (msg.MessageType === 'ForceKeepAlive') {\n console.debug(`Received ForceKeepAlive from server. Timeout is ${msg.Data} seconds.`);\n instance.sendWebSocketMessage('KeepAlive');\n scheduleKeepAlive(instance, msg.Data);\n }\n\n events.trigger(instance, 'message', [msg]);\n}\n\n/**\n * Starts a poller that sends KeepAlive messages using a WebSocket connection.\n * @param {Object} instance The WebSocket connection.\n * @param {number} timeout The number of seconds after which the WebSocket is considered lost by the server.\n * @returns {number} The id of the interval.\n * @since 10.6.0\n */\nfunction scheduleKeepAlive(instance, timeout) {\n clearKeepAlive(instance);\n instance.keepAliveInterval = setInterval(() => {\n instance.sendWebSocketMessage('KeepAlive');\n }, timeout * 1000 * 0.5);\n return instance.keepAliveInterval;\n}\n\n/**\n * Stops the poller that is sending KeepAlive messages on a WebSocket connection.\n * @param {Object} instance The WebSocket connection.\n * @since 10.6.0\n */\nfunction clearKeepAlive(instance) {\n console.debug('Clearing KeepAlive for', instance);\n if (instance.keepAliveInterval) {\n clearInterval(instance.keepAliveInterval);\n instance.keepAliveInterval = null;\n }\n}\n\nfunction onWebSocketOpen() {\n const instance = this;\n console.log('web socket connection opened');\n events.trigger(instance, 'websocketopen');\n}\n\nfunction onWebSocketError() {\n const instance = this;\n clearKeepAlive(instance);\n events.trigger(instance, 'websocketerror');\n}\n\nfunction setSocketOnClose(apiClient, socket) {\n socket.onclose = () => {\n console.log('web socket closed');\n\n clearKeepAlive(socket);\n if (apiClient._webSocket === socket) {\n console.log('nulling out web socket');\n apiClient._webSocket = null;\n }\n\n setTimeout(() => {\n events.trigger(apiClient, 'websocketclose');\n }, 0);\n };\n}\n\nfunction normalizeReturnBitrate(instance, bitrate) {\n if (!bitrate) {\n if (instance.lastDetectedBitrate) {\n return instance.lastDetectedBitrate;\n }\n\n return Promise.reject();\n }\n\n let result = Math.round(bitrate * 0.7);\n\n // allow configuration of this\n if (instance.getMaxBandwidth) {\n const maxRate = instance.getMaxBandwidth();\n if (maxRate) {\n result = Math.min(result, maxRate);\n }\n }\n\n instance.lastDetectedBitrate = result;\n instance.lastDetectedBitrateTime = new Date().getTime();\n\n return result;\n}\n\nfunction detectBitrateInternal(instance, tests, index, currentBitrate) {\n if (index >= tests.length) {\n return normalizeReturnBitrate(instance, currentBitrate);\n }\n\n const test = tests[index];\n\n return instance.getDownloadSpeed(test.bytes).then(\n (bitrate) => {\n if (bitrate < test.threshold) {\n return normalizeReturnBitrate(instance, bitrate);\n } else {\n return detectBitrateInternal(instance, tests, index + 1, bitrate);\n }\n },\n () => normalizeReturnBitrate(instance, currentBitrate)\n );\n}\n\nfunction detectBitrateWithEndpointInfo(instance, endpointInfo) {\n if (endpointInfo.IsInNetwork) {\n const result = 140000000;\n instance.lastDetectedBitrate = result;\n instance.lastDetectedBitrateTime = new Date().getTime();\n return result;\n }\n\n return detectBitrateInternal(\n instance,\n [\n {\n bytes: 500000,\n threshold: 500000\n },\n {\n bytes: 1000000,\n threshold: 20000000\n },\n {\n bytes: 3000000,\n threshold: 50000000\n }\n ],\n 0\n );\n}\n\nfunction getRemoteImagePrefix(instance, options) {\n let urlPrefix;\n\n if (options.artist) {\n urlPrefix = `Artists/${instance.encodeName(options.artist)}`;\n delete options.artist;\n } else if (options.person) {\n urlPrefix = `Persons/${instance.encodeName(options.person)}`;\n delete options.person;\n } else if (options.genre) {\n urlPrefix = `Genres/${instance.encodeName(options.genre)}`;\n delete options.genre;\n } else if (options.musicGenre) {\n urlPrefix = `MusicGenres/${instance.encodeName(options.musicGenre)}`;\n delete options.musicGenre;\n } else if (options.studio) {\n urlPrefix = `Studios/${instance.encodeName(options.studio)}`;\n delete options.studio;\n } else {\n urlPrefix = `Items/${options.itemId}`;\n delete options.itemId;\n }\n\n return urlPrefix;\n}\n\nfunction normalizeImageOptions(instance, options) {\n let ratio = window.devicePixelRatio || 1;\n\n if (ratio) {\n if (options.minScale) {\n ratio = Math.max(options.minScale, ratio);\n }\n\n if (options.width) {\n options.width = Math.round(options.width * ratio);\n }\n if (options.height) {\n options.height = Math.round(options.height * ratio);\n }\n if (options.maxWidth) {\n options.maxWidth = Math.round(options.maxWidth * ratio);\n }\n if (options.maxHeight) {\n options.maxHeight = Math.round(options.maxHeight * ratio);\n }\n }\n\n options.quality = options.quality || instance.getDefaultImageQuality(options.type);\n\n if (instance.normalizeImageOptions) {\n instance.normalizeImageOptions(options);\n }\n}\n\nfunction compareVersions(a, b) {\n // -1 a is smaller\n // 1 a is larger\n // 0 equal\n a = a.split('.');\n b = b.split('.');\n\n for (let i = 0, length = Math.max(a.length, b.length); i < length; i++) {\n const aVal = parseInt(a[i] || '0');\n const bVal = parseInt(b[i] || '0');\n\n if (aVal < bVal) {\n return -1;\n }\n\n if (aVal > bVal) {\n return 1;\n }\n }\n\n return 0;\n}\n\nexport default ApiClient;\n","import ApiClient from './apiClient';\n\nconst localPrefix = 'local:';\nconst localViewPrefix = 'localview:';\n\nfunction isLocalId(str) {\n return startsWith(str, localPrefix);\n}\n\nfunction isLocalViewId(str) {\n return startsWith(str, localViewPrefix);\n}\n\nfunction isTopLevelLocalViewId(str) {\n return str === 'localview';\n}\n\nfunction stripLocalPrefix(str) {\n let res = stripStart(str, localPrefix);\n res = stripStart(res, localViewPrefix);\n\n return res;\n}\n\nfunction startsWith(str, find) {\n if (str && find && str.length > find.length) {\n if (str.indexOf(find) === 0) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction stripStart(str, find) {\n if (startsWith(str, find)) {\n return str.substr(find.length);\n }\n\n return str;\n}\n\nfunction createEmptyList() {\n const result = {\n Items: [],\n TotalRecordCount: 0\n };\n\n return result;\n}\n\nfunction convertGuidToLocal(guid) {\n if (!guid) {\n return null;\n }\n\n if (isLocalId(guid)) {\n return guid;\n }\n\n return `local:${guid}`;\n}\n\nfunction adjustGuidProperties(downloadedItem) {\n downloadedItem.Id = convertGuidToLocal(downloadedItem.Id);\n downloadedItem.SeriesId = convertGuidToLocal(downloadedItem.SeriesId);\n downloadedItem.SeasonId = convertGuidToLocal(downloadedItem.SeasonId);\n\n downloadedItem.AlbumId = convertGuidToLocal(downloadedItem.AlbumId);\n downloadedItem.ParentId = convertGuidToLocal(downloadedItem.ParentId);\n downloadedItem.ParentThumbItemId = convertGuidToLocal(downloadedItem.ParentThumbItemId);\n downloadedItem.ParentPrimaryImageItemId = convertGuidToLocal(downloadedItem.ParentPrimaryImageItemId);\n downloadedItem.PrimaryImageItemId = convertGuidToLocal(downloadedItem.PrimaryImageItemId);\n downloadedItem.ParentLogoItemId = convertGuidToLocal(downloadedItem.ParentLogoItemId);\n downloadedItem.ParentBackdropItemId = convertGuidToLocal(downloadedItem.ParentBackdropItemId);\n\n downloadedItem.ParentBackdropImageTags = null;\n}\n\nfunction getLocalView(instance, serverId, userId) {\n return instance.getLocalFolders(serverId, userId).then((views) => {\n let localView = null;\n\n if (views.length > 0) {\n localView = {\n Name: instance.downloadsTitleText || 'Downloads',\n ServerId: serverId,\n Id: 'localview',\n Type: 'localview',\n IsFolder: true\n };\n }\n\n return Promise.resolve(localView);\n });\n}\n\n/**\n * Creates a new api client instance\n * @param {String} serverAddress\n * @param {String} clientName s\n * @param {String} applicationVersion\n */\nclass ApiClientCore extends ApiClient {\n constructor(\n serverAddress,\n clientName,\n applicationVersion,\n deviceName,\n deviceId,\n devicePixelRatio,\n localAssetManager\n ) {\n super(serverAddress, clientName, applicationVersion, deviceName, deviceId, devicePixelRatio);\n this.localAssetManager = localAssetManager;\n }\n\n getPlaybackInfo(itemId, options, deviceProfile) {\n const onFailure = () => ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile);\n\n if (isLocalId(itemId)) {\n return this.localAssetManager.getLocalItem(this.serverId(), stripLocalPrefix(itemId)).then((item) => {\n // TODO: This was already done during the sync process, right? If so, remove it\n const mediaSources = item.Item.MediaSources.map((m) => {\n m.SupportsDirectPlay = true;\n m.SupportsDirectStream = false;\n m.SupportsTranscoding = false;\n m.IsLocal = true;\n return m;\n });\n\n return {\n MediaSources: mediaSources\n };\n }, onFailure);\n }\n\n var instance = this;\n return this.localAssetManager.getLocalItem(this.serverId(), itemId).then((item) => {\n if (item) {\n const mediaSources = item.Item.MediaSources.map((m) => {\n m.SupportsDirectPlay = true;\n m.SupportsDirectStream = false;\n m.SupportsTranscoding = false;\n m.IsLocal = true;\n return m;\n });\n\n return instance.localAssetManager.fileExists(item.LocalPath).then((exists) => {\n if (exists) {\n const res = {\n MediaSources: mediaSources\n };\n\n return Promise.resolve(res);\n }\n\n return ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile);\n }, onFailure);\n }\n\n return ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile);\n }, onFailure);\n }\n\n getItems(userId, options) {\n const serverInfo = this.serverInfo();\n let i;\n\n if (serverInfo && options.ParentId === 'localview') {\n return this.getLocalFolders(serverInfo.Id, userId).then((items) => {\n const result = {\n Items: items,\n TotalRecordCount: items.length\n };\n\n return Promise.resolve(result);\n });\n } else if (\n serverInfo &&\n options &&\n (isLocalId(options.ParentId) ||\n isLocalId(options.SeriesId) ||\n isLocalId(options.SeasonId) ||\n isLocalViewId(options.ParentId) ||\n isLocalId(options.AlbumIds))\n ) {\n return this.localAssetManager.getViewItems(serverInfo.Id, userId, options).then((items) => {\n items.forEach((item) => {\n adjustGuidProperties(item);\n });\n\n const result = {\n Items: items,\n TotalRecordCount: items.length\n };\n\n return Promise.resolve(result);\n });\n } else if (options && options.ExcludeItemIds && options.ExcludeItemIds.length) {\n const exItems = options.ExcludeItemIds.split(',');\n\n for (i = 0; i < exItems.length; i++) {\n if (isLocalId(exItems[i])) {\n return Promise.resolve(createEmptyList());\n }\n }\n } else if (options && options.Ids && options.Ids.length) {\n const ids = options.Ids.split(',');\n let hasLocal = false;\n\n for (i = 0; i < ids.length; i++) {\n if (isLocalId(ids[i])) {\n hasLocal = true;\n }\n }\n\n if (hasLocal) {\n return this.localAssetManager.getItemsFromIds(serverInfo.Id, ids).then((items) => {\n items.forEach((item) => {\n adjustGuidProperties(item);\n });\n\n const result = {\n Items: items,\n TotalRecordCount: items.length\n };\n\n return Promise.resolve(result);\n });\n }\n }\n\n return ApiClient.prototype.getItems.call(this, userId, options);\n }\n\n getUserViews(options, userId) {\n const instance = this;\n\n options = options || {};\n\n const basePromise = ApiClient.prototype.getUserViews.call(instance, options, userId);\n\n if (!options.enableLocalView) {\n return basePromise;\n }\n\n return basePromise.then((result) => {\n const serverInfo = instance.serverInfo();\n if (serverInfo) {\n return getLocalView(instance, serverInfo.Id, userId).then((localView) => {\n if (localView) {\n result.Items.push(localView);\n result.TotalRecordCount++;\n }\n\n return Promise.resolve(result);\n });\n }\n\n return Promise.resolve(result);\n });\n }\n\n getItem(userId, itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n if (itemId) {\n itemId = itemId.toString();\n }\n\n let serverInfo;\n\n if (isTopLevelLocalViewId(itemId)) {\n serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return getLocalView(this, serverInfo.Id, userId);\n }\n }\n\n if (isLocalViewId(itemId)) {\n serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.getLocalFolders(serverInfo.Id, userId).then((items) => {\n const views = items.filter((item) => item.Id === itemId);\n\n if (views.length > 0) {\n return Promise.resolve(views[0]);\n }\n\n // TODO: Test consequence of this\n return Promise.reject();\n });\n }\n }\n\n if (isLocalId(itemId)) {\n serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.localAssetManager.getLocalItem(serverInfo.Id, stripLocalPrefix(itemId)).then((item) => {\n adjustGuidProperties(item.Item);\n\n return Promise.resolve(item.Item);\n });\n }\n }\n\n return ApiClient.prototype.getItem.call(this, userId, itemId);\n }\n\n getLocalFolders(userId) {\n const serverInfo = this.serverInfo();\n userId = userId || serverInfo.UserId;\n\n return this.localAssetManager.getViews(serverInfo.Id, userId);\n }\n\n getNextUpEpisodes(options) {\n if (options.SeriesId) {\n if (isLocalId(options.SeriesId)) {\n return Promise.resolve(createEmptyList());\n }\n }\n\n return ApiClient.prototype.getNextUpEpisodes.call(this, options);\n }\n\n getSeasons(itemId, options) {\n if (isLocalId(itemId)) {\n options.SeriesId = itemId;\n options.IncludeItemTypes = 'Season';\n return this.getItems(this.getCurrentUserId(), options);\n }\n\n return ApiClient.prototype.getSeasons.call(this, itemId, options);\n }\n\n getEpisodes(itemId, options) {\n if (isLocalId(options.SeasonId) || isLocalId(options.seasonId)) {\n options.SeriesId = itemId;\n options.IncludeItemTypes = 'Episode';\n return this.getItems(this.getCurrentUserId(), options);\n }\n\n // get episodes by recursion\n if (isLocalId(itemId)) {\n options.SeriesId = itemId;\n options.IncludeItemTypes = 'Episode';\n return this.getItems(this.getCurrentUserId(), options);\n }\n\n return ApiClient.prototype.getEpisodes.call(this, itemId, options);\n }\n\n getLatestOfflineItems(options) {\n // Supported options\n // MediaType - Audio/Video/Photo/Book/Game\n // Limit\n // Filters: 'IsNotFolder' or 'IsFolder'\n\n options.SortBy = 'DateCreated';\n options.SortOrder = 'Descending';\n\n const serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.localAssetManager.getViewItems(serverInfo.Id, null, options).then((items) => {\n items.forEach((item) => {\n adjustGuidProperties(item);\n });\n\n return Promise.resolve(items);\n });\n }\n\n return Promise.resolve([]);\n }\n\n getThemeMedia(userId, itemId, inherit) {\n if (isLocalViewId(itemId) || isLocalId(itemId) || isTopLevelLocalViewId(itemId)) {\n return Promise.reject();\n }\n\n return ApiClient.prototype.getThemeMedia.call(this, userId, itemId, inherit);\n }\n\n getSpecialFeatures(userId, itemId) {\n if (isLocalId(itemId)) {\n return Promise.resolve([]);\n }\n\n return ApiClient.prototype.getSpecialFeatures.call(this, userId, itemId);\n }\n\n getSimilarItems(itemId, options) {\n if (isLocalId(itemId)) {\n return Promise.resolve(createEmptyList());\n }\n\n return ApiClient.prototype.getSimilarItems.call(this, itemId, options);\n }\n\n updateFavoriteStatus(userId, itemId, isFavorite) {\n if (isLocalId(itemId)) {\n return Promise.resolve();\n }\n\n return ApiClient.prototype.updateFavoriteStatus.call(this, userId, itemId, isFavorite);\n }\n\n getScaledImageUrl(itemId, options) {\n if (isLocalId(itemId) || (options && options.itemid && isLocalId(options.itemid))) {\n const serverInfo = this.serverInfo();\n const id = stripLocalPrefix(itemId);\n\n return this.localAssetManager.getImageUrl(serverInfo.Id, id, options);\n }\n\n return ApiClient.prototype.getScaledImageUrl.call(this, itemId, options);\n }\n\n reportPlaybackStart(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n if (isLocalId(options.ItemId)) {\n return Promise.resolve();\n }\n\n return ApiClient.prototype.reportPlaybackStart.call(this, options);\n }\n\n reportPlaybackProgress(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n if (isLocalId(options.ItemId)) {\n const serverInfo = this.serverInfo();\n\n if (serverInfo) {\n const instance = this;\n return this.localAssetManager\n .getLocalItem(serverInfo.Id, stripLocalPrefix(options.ItemId))\n .then((item) => {\n const libraryItem = item.Item;\n\n if (libraryItem.MediaType === 'Video' || libraryItem.Type === 'AudioBook') {\n libraryItem.UserData = libraryItem.UserData || {};\n libraryItem.UserData.PlaybackPositionTicks = options.PositionTicks;\n libraryItem.UserData.PlayedPercentage = Math.min(\n libraryItem.RunTimeTicks\n ? 100 * ((options.PositionTicks || 0) / libraryItem.RunTimeTicks)\n : 0,\n 100\n );\n return instance.localAssetManager.addOrUpdateLocalItem(item);\n }\n\n return Promise.resolve();\n });\n }\n\n return Promise.resolve();\n }\n\n return ApiClient.prototype.reportPlaybackProgress.call(this, options);\n }\n\n reportPlaybackStopped(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n if (isLocalId(options.ItemId)) {\n const serverInfo = this.serverInfo();\n\n const action = {\n Date: new Date().getTime(),\n ItemId: stripLocalPrefix(options.ItemId),\n PositionTicks: options.PositionTicks,\n ServerId: serverInfo.Id,\n Type: 0, // UserActionType.PlayedItem\n UserId: this.getCurrentUserId()\n };\n\n return this.localAssetManager.recordUserAction(action);\n }\n\n return ApiClient.prototype.reportPlaybackStopped.call(this, options);\n }\n\n getIntros(itemId) {\n if (isLocalId(itemId)) {\n return Promise.resolve({\n Items: [],\n TotalRecordCount: 0\n });\n }\n\n return ApiClient.prototype.getIntros.call(this, itemId);\n }\n\n getInstantMixFromItem(itemId, options) {\n if (isLocalId(itemId)) {\n return Promise.resolve({\n Items: [],\n TotalRecordCount: 0\n });\n }\n\n return ApiClient.prototype.getInstantMixFromItem.call(this, itemId, options);\n }\n\n getItemDownloadUrl(itemId) {\n if (isLocalId(itemId)) {\n const serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.localAssetManager\n .getLocalItem(serverInfo.Id, stripLocalPrefix(itemId))\n .then((item) => Promise.resolve(item.LocalPath));\n }\n }\n\n return ApiClient.prototype.getItemDownloadUrl.call(this, itemId);\n }\n}\n\nexport default ApiClientCore;\n","import events from './events';\nimport ApiClient from './apiClient';\n\nconst defaultTimeout = 20000;\n\nconst ConnectionMode = {\n Local: 0,\n Remote: 1,\n Manual: 2\n};\n\nfunction getServerAddress(server, mode) {\n switch (mode) {\n case ConnectionMode.Local:\n return server.LocalAddress;\n case ConnectionMode.Manual:\n return server.ManualAddress;\n case ConnectionMode.Remote:\n return server.RemoteAddress;\n default:\n return server.ManualAddress || server.LocalAddress || server.RemoteAddress;\n }\n}\n\nfunction paramsToString(params) {\n const values = [];\n\n for (const key in params) {\n const value = params[key];\n\n if (value !== null && value !== undefined && value !== '') {\n values.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);\n }\n }\n return values.join('&');\n}\n\nfunction resolveFailure(instance, resolve) {\n resolve({\n State: 'Unavailable'\n });\n}\n\nfunction mergeServers(credentialProvider, list1, list2) {\n for (let i = 0, length = list2.length; i < length; i++) {\n credentialProvider.addOrUpdateServer(list1, list2[i]);\n }\n\n return list1;\n}\n\nfunction updateServerInfo(server, systemInfo) {\n server.Name = systemInfo.ServerName;\n\n if (systemInfo.Id) {\n server.Id = systemInfo.Id;\n }\n if (systemInfo.LocalAddress) {\n server.LocalAddress = systemInfo.LocalAddress;\n }\n}\n\nfunction getEmbyServerUrl(baseUrl, handler) {\n return `${baseUrl}/${handler}`;\n}\n\nfunction getFetchPromise(request) {\n const headers = request.headers || {};\n\n if (request.dataType === 'json') {\n headers.accept = 'application/json';\n }\n\n const fetchRequest = {\n headers,\n method: request.type,\n credentials: 'same-origin'\n };\n\n let contentType = request.contentType;\n\n if (request.data) {\n if (typeof request.data === 'string') {\n fetchRequest.body = request.data;\n } else {\n fetchRequest.body = paramsToString(request.data);\n\n contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';\n }\n }\n\n if (contentType) {\n headers['Content-Type'] = contentType;\n }\n\n if (!request.timeout) {\n return fetch(request.url, fetchRequest);\n }\n\n return fetchWithTimeout(request.url, fetchRequest, request.timeout);\n}\n\nfunction fetchWithTimeout(url, options, timeoutMs) {\n console.log(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`);\n\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(reject, timeoutMs);\n\n options = options || {};\n options.credentials = 'same-origin';\n\n fetch(url, options).then(\n (response) => {\n clearTimeout(timeout);\n\n console.log(`fetchWithTimeout: succeeded connecting to url: ${url}`);\n\n resolve(response);\n },\n (error) => {\n clearTimeout(timeout);\n\n console.log(`fetchWithTimeout: timed out connecting to url: ${url}`);\n\n reject();\n }\n );\n });\n}\n\nfunction ajax(request) {\n if (!request) {\n throw new Error('Request cannot be null');\n }\n\n request.headers = request.headers || {};\n\n console.log(`ConnectionManager requesting url: ${request.url}`);\n\n return getFetchPromise(request).then(\n (response) => {\n console.log(`ConnectionManager response status: ${response.status}, url: ${request.url}`);\n\n if (response.status < 400) {\n if (request.dataType === 'json' || request.headers.accept === 'application/json') {\n return response.json();\n } else {\n return response;\n }\n } else {\n return Promise.reject(response);\n }\n },\n (err) => {\n console.log(`ConnectionManager request failed to url: ${request.url}`);\n throw err;\n }\n );\n}\n\nfunction replaceAll(originalString, strReplace, strWith) {\n const reg = new RegExp(strReplace, 'ig');\n return originalString.replace(reg, strWith);\n}\n\nfunction normalizeAddress(address) {\n // Attempt to correct bad input\n address = address.trim();\n\n // Seeing failures in iOS when protocol isn't lowercase\n address = replaceAll(address, 'Http:', 'http:');\n address = replaceAll(address, 'Https:', 'https:');\n\n return address;\n}\n\nfunction stringEqualsIgnoreCase(str1, str2) {\n return (str1 || '').toLowerCase() === (str2 || '').toLowerCase();\n}\n\nfunction compareVersions(a, b) {\n // -1 a is smaller\n // 1 a is larger\n // 0 equal\n a = a.split('.');\n b = b.split('.');\n\n for (let i = 0, length = Math.max(a.length, b.length); i < length; i++) {\n const aVal = parseInt(a[i] || '0');\n const bVal = parseInt(b[i] || '0');\n\n if (aVal < bVal) {\n return -1;\n }\n\n if (aVal > bVal) {\n return 1;\n }\n }\n\n return 0;\n}\n\nexport default class ConnectionManager {\n constructor(credentialProvider, appName, appVersion, deviceName, deviceId, capabilities) {\n console.log('Begin ConnectionManager constructor');\n\n const self = this;\n this._apiClients = [];\n\n self._minServerVersion = '3.2.33';\n\n self.appVersion = () => appVersion;\n\n self.appName = () => appName;\n\n self.capabilities = () => capabilities;\n\n self.deviceId = () => deviceId;\n\n self.credentialProvider = () => credentialProvider;\n\n self.getServerInfo = (id) => {\n const servers = credentialProvider.credentials().Servers;\n\n return servers.filter((s) => s.Id === id)[0];\n };\n\n self.getLastUsedServer = () => {\n const servers = credentialProvider.credentials().Servers;\n\n servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));\n\n if (!servers.length) {\n return null;\n }\n\n return servers[0];\n };\n\n self.addApiClient = (apiClient) => {\n self._apiClients.push(apiClient);\n\n const existingServers = credentialProvider\n .credentials()\n .Servers.filter(\n (s) =>\n stringEqualsIgnoreCase(s.ManualAddress, apiClient.serverAddress()) ||\n stringEqualsIgnoreCase(s.LocalAddress, apiClient.serverAddress()) ||\n stringEqualsIgnoreCase(s.RemoteAddress, apiClient.serverAddress())\n );\n\n const existingServer = existingServers.length ? existingServers[0] : apiClient.serverInfo();\n existingServer.DateLastAccessed = new Date().getTime();\n existingServer.LastConnectionMode = ConnectionMode.Manual;\n existingServer.ManualAddress = apiClient.serverAddress();\n\n if (apiClient.manualAddressOnly) {\n existingServer.manualAddressOnly = true;\n }\n\n apiClient.serverInfo(existingServer);\n\n apiClient.onAuthenticated = (instance, result) => onAuthenticated(instance, result, {}, true);\n\n if (!existingServers.length) {\n const credentials = credentialProvider.credentials();\n credentials.Servers = [existingServer];\n credentialProvider.credentials(credentials);\n }\n\n events.trigger(self, 'apiclientcreated', [apiClient]);\n };\n\n self.clearData = () => {\n console.log('connection manager clearing data');\n\n const credentials = credentialProvider.credentials();\n credentials.Servers = [];\n credentialProvider.credentials(credentials);\n };\n\n self._getOrAddApiClient = (server, serverUrl) => {\n let apiClient = self.getApiClient(server.Id);\n\n if (!apiClient) {\n apiClient = new ApiClient(serverUrl, appName, appVersion, deviceName, deviceId);\n\n self._apiClients.push(apiClient);\n\n apiClient.serverInfo(server);\n\n apiClient.onAuthenticated = (instance, result) => {\n return onAuthenticated(instance, result, {}, true);\n };\n\n events.trigger(self, 'apiclientcreated', [apiClient]);\n }\n\n console.log('returning instance from getOrAddApiClient');\n return apiClient;\n };\n\n self.getOrCreateApiClient = (serverId) => {\n const credentials = credentialProvider.credentials();\n const servers = credentials.Servers.filter((s) => stringEqualsIgnoreCase(s.Id, serverId));\n\n if (!servers.length) {\n throw new Error(`Server not found: ${serverId}`);\n }\n\n const server = servers[0];\n\n return self._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode));\n };\n\n function onAuthenticated(apiClient, result, options, saveCredentials) {\n const credentials = credentialProvider.credentials();\n const servers = credentials.Servers.filter((s) => s.Id === result.ServerId);\n\n const server = servers.length ? servers[0] : apiClient.serverInfo();\n\n if (options.updateDateLastAccessed !== false) {\n server.DateLastAccessed = new Date().getTime();\n }\n server.Id = result.ServerId;\n\n if (saveCredentials) {\n server.UserId = result.User.Id;\n server.AccessToken = result.AccessToken;\n } else {\n server.UserId = null;\n server.AccessToken = null;\n }\n\n credentialProvider.addOrUpdateServer(credentials.Servers, server);\n credentialProvider.credentials(credentials);\n\n // set this now before updating server info, otherwise it won't be set in time\n apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;\n\n apiClient.serverInfo(server);\n afterConnected(apiClient, options);\n\n return onLocalUserSignIn(server, apiClient.serverAddress(), result.User);\n }\n\n function afterConnected(apiClient, options = {}) {\n if (options.reportCapabilities !== false) {\n apiClient.reportCapabilities(capabilities);\n }\n apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;\n\n if (options.enableWebSocket !== false) {\n console.log('calling apiClient.ensureWebSocket');\n\n apiClient.ensureWebSocket();\n }\n }\n\n function onLocalUserSignIn(server, serverUrl, user) {\n // Ensure this is created so that listeners of the event can get the apiClient instance\n self._getOrAddApiClient(server, serverUrl);\n\n // This allows the app to have a single hook that fires before any other\n const promise = self.onLocalUserSignedIn ? self.onLocalUserSignedIn.call(self, user) : Promise.resolve();\n\n return promise.then(() => {\n events.trigger(self, 'localusersignedin', [user]);\n });\n }\n\n function validateAuthentication(server, serverUrl) {\n return ajax({\n type: 'GET',\n url: getEmbyServerUrl(serverUrl, 'System/Info'),\n dataType: 'json',\n headers: {\n 'X-MediaBrowser-Token': server.AccessToken\n }\n }).then(\n (systemInfo) => {\n updateServerInfo(server, systemInfo);\n return Promise.resolve();\n },\n () => {\n server.UserId = null;\n server.AccessToken = null;\n return Promise.resolve();\n }\n );\n }\n\n function getImageUrl(localUser) {\n if (localUser && localUser.PrimaryImageTag) {\n const apiClient = self.getApiClient(localUser);\n\n const url = apiClient.getUserImageUrl(localUser.Id, {\n tag: localUser.PrimaryImageTag,\n type: 'Primary'\n });\n\n return {\n url,\n supportsParams: true\n };\n }\n\n return {\n url: null,\n supportsParams: false\n };\n }\n\n self.user = (apiClient) =>\n new Promise((resolve, reject) => {\n let localUser;\n\n function onLocalUserDone(e) {\n if (apiClient && apiClient.getCurrentUserId()) {\n apiClient.getCurrentUser().then((u) => {\n localUser = u;\n const image = getImageUrl(localUser);\n\n resolve({\n localUser,\n name: localUser ? localUser.Name : null,\n imageUrl: image.url,\n supportsImageParams: image.supportsParams\n });\n });\n }\n }\n\n if (apiClient && apiClient.getCurrentUserId()) {\n onLocalUserDone();\n }\n });\n\n self.logout = () => {\n const promises = [];\n\n for (let i = 0, length = self._apiClients.length; i < length; i++) {\n const apiClient = self._apiClients[i];\n\n if (apiClient.accessToken()) {\n promises.push(logoutOfServer(apiClient));\n }\n }\n\n return Promise.all(promises).then(() => {\n const credentials = credentialProvider.credentials();\n\n const servers = credentials.Servers.filter((u) => u.UserLinkType !== 'Guest');\n\n for (let j = 0, numServers = servers.length; j < numServers; j++) {\n const server = servers[j];\n\n server.UserId = null;\n server.AccessToken = null;\n server.ExchangeToken = null;\n }\n });\n };\n\n function logoutOfServer(apiClient) {\n const serverInfo = apiClient.serverInfo() || {};\n\n const logoutInfo = {\n serverId: serverInfo.Id\n };\n\n return apiClient.logout().then(\n () => {\n events.trigger(self, 'localusersignedout', [logoutInfo]);\n },\n () => {\n events.trigger(self, 'localusersignedout', [logoutInfo]);\n }\n );\n }\n\n self.getSavedServers = () => {\n const credentials = credentialProvider.credentials();\n\n const servers = credentials.Servers.slice(0);\n\n servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));\n\n return servers;\n };\n\n self.getAvailableServers = () => {\n console.log('Begin getAvailableServers');\n\n // Clone the array\n const credentials = credentialProvider.credentials();\n\n return Promise.all([findServers()]).then((responses) => {\n const foundServers = responses[0];\n let servers = credentials.Servers.slice(0);\n mergeServers(credentialProvider, servers, foundServers);\n\n servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));\n credentials.Servers = servers;\n credentialProvider.credentials(credentials);\n\n return servers;\n });\n };\n\n function findServers() {\n return new Promise((resolve, reject) => {\n var onFinish = function (foundServers) {\n var servers = foundServers.map((foundServer) => {\n var info = {\n Id: foundServer.Id,\n LocalAddress: convertEndpointAddressToManualAddress(foundServer) || foundServer.Address,\n Name: foundServer.Name\n };\n info.LastConnectionMode = info.ManualAddress ? ConnectionMode.Manual : ConnectionMode.Local;\n return info;\n });\n resolve(servers);\n };\n\n if (window.NativeShell && typeof window.NativeShell.findServers === 'function') {\n window.NativeShell.findServers(1e3).then(onFinish, function () {\n onFinish([]);\n });\n } else {\n resolve([]);\n }\n });\n }\n\n function convertEndpointAddressToManualAddress(info) {\n if (info.Address && info.EndpointAddress) {\n let address = info.EndpointAddress.split(':')[0];\n\n // Determine the port, if any\n const parts = info.Address.split(':');\n if (parts.length > 1) {\n const portString = parts[parts.length - 1];\n\n if (!isNaN(parseInt(portString))) {\n address += `:${portString}`;\n }\n }\n\n return normalizeAddress(address);\n }\n\n return null;\n }\n\n self.connectToServers = (servers, options) => {\n console.log(`Begin connectToServers, with ${servers.length} servers`);\n\n const firstServer = servers.length ? servers[0] : null;\n // See if we have any saved credentials and can auto sign in\n if (firstServer) {\n return self.connectToServer(firstServer, options).then((result) => {\n if (result.State === 'Unavailable') {\n result.State = 'ServerSelection';\n }\n\n console.log('resolving connectToServers with result.State: ' + result.State);\n return result;\n });\n }\n\n return Promise.resolve({\n Servers: servers,\n State: 'ServerSelection'\n });\n };\n\n function getTryConnectPromise(url, connectionMode, state, resolve, reject) {\n console.log('getTryConnectPromise ' + url);\n\n ajax({\n url: getEmbyServerUrl(url, 'system/info/public'),\n timeout: defaultTimeout,\n type: 'GET',\n dataType: 'json'\n }).then(\n (result) => {\n if (!state.resolved) {\n state.resolved = true;\n\n console.log('Reconnect succeeded to ' + url);\n resolve({\n url: url,\n connectionMode: connectionMode,\n data: result\n });\n }\n },\n () => {\n console.log('Reconnect failed to ' + url);\n\n if (!state.resolved) {\n state.rejects++;\n if (state.rejects >= state.numAddresses) {\n reject();\n }\n }\n }\n );\n }\n\n function tryReconnect(serverInfo) {\n const addresses = [];\n const addressesStrings = [];\n\n // the timeouts are a small hack to try and ensure the remote address doesn't resolve first\n\n // manualAddressOnly is used for the local web app that always connects to a fixed address\n if (\n !serverInfo.manualAddressOnly &&\n serverInfo.LocalAddress &&\n addressesStrings.indexOf(serverInfo.LocalAddress) === -1\n ) {\n addresses.push({\n url: serverInfo.LocalAddress,\n mode: ConnectionMode.Local,\n timeout: 0\n });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (serverInfo.ManualAddress && addressesStrings.indexOf(serverInfo.ManualAddress) === -1) {\n addresses.push({\n url: serverInfo.ManualAddress,\n mode: ConnectionMode.Manual,\n timeout: 100\n });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (\n !serverInfo.manualAddressOnly &&\n serverInfo.RemoteAddress &&\n addressesStrings.indexOf(serverInfo.RemoteAddress) === -1\n ) {\n addresses.push({\n url: serverInfo.RemoteAddress,\n mode: ConnectionMode.Remote,\n timeout: 200\n });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n\n console.log('tryReconnect: ' + addressesStrings.join('|'));\n\n return new Promise((resolve, reject) => {\n const state = {};\n state.numAddresses = addresses.length;\n state.rejects = 0;\n\n addresses.map((url) => {\n setTimeout(() => {\n if (!state.resolved) {\n getTryConnectPromise(url.url, url.mode, state, resolve, reject);\n }\n }, url.timeout);\n });\n });\n }\n\n self.connectToServer = (server, options) => {\n console.log('begin connectToServer');\n\n return new Promise((resolve, reject) => {\n options = options || {};\n\n tryReconnect(server).then(\n (result) => {\n const serverUrl = result.url;\n const connectionMode = result.connectionMode;\n result = result.data;\n\n if (compareVersions(self.minServerVersion(), result.Version) === 1) {\n console.log('minServerVersion requirement not met. Server version: ' + result.Version);\n resolve({\n State: 'ServerUpdateNeeded',\n Servers: [server]\n });\n } else if (server.Id && result.Id !== server.Id) {\n console.log(\n 'http request succeeded, but found a different server Id than what was expected'\n );\n resolveFailure(self, resolve);\n } else {\n onSuccessfulConnection(server, result, connectionMode, serverUrl, true, resolve, options);\n }\n },\n () => {\n resolveFailure(self, resolve);\n }\n );\n });\n };\n\n function onSuccessfulConnection(server, systemInfo, connectionMode, serverUrl, verifyLocalAuthentication, resolve, options={}) {\n const credentials = credentialProvider.credentials();\n\n if (options.enableAutoLogin === false) {\n server.UserId = null;\n server.AccessToken = null;\n } else if (server.AccessToken && verifyLocalAuthentication) {\n return void validateAuthentication(server, serverUrl).then(function () {\n onSuccessfulConnection(server, systemInfo, connectionMode, serverUrl, false, resolve, options);\n });\n }\n\n updateServerInfo(server, systemInfo);\n\n server.LastConnectionMode = connectionMode;\n\n if (options.updateDateLastAccessed !== false) {\n server.DateLastAccessed = new Date().getTime();\n }\n credentialProvider.addOrUpdateServer(credentials.Servers, server);\n credentialProvider.credentials(credentials);\n\n const result = {\n Servers: []\n };\n\n result.ApiClient = self._getOrAddApiClient(server, serverUrl);\n\n result.ApiClient.setSystemInfo(systemInfo);\n\n result.State = server.AccessToken && options.enableAutoLogin !== false ? 'SignedIn' : 'ServerSignIn';\n\n result.Servers.push(server);\n\n // set this now before updating server info, otherwise it won't be set in time\n result.ApiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;\n\n result.ApiClient.updateServerInfo(server, serverUrl);\n\n const resolveActions = function () {\n resolve(result);\n\n events.trigger(self, 'connected', [result]);\n };\n\n if (result.State === 'SignedIn') {\n afterConnected(result.ApiClient, options);\n\n result.ApiClient.getCurrentUser().then((user) => {\n onLocalUserSignIn(server, serverUrl, user).then(resolveActions, resolveActions);\n }, resolveActions);\n } else {\n resolveActions();\n }\n }\n\n function tryConnectToAddress(address, options) {\n const server = {\n ManualAddress: address,\n LastConnectionMode: ConnectionMode.Manual\n };\n\n return self.connectToServer(server, options).then((result) => {\n // connectToServer never rejects, but resolves with State='Unavailable'\n if (result.State === 'Unavailable') {\n return Promise.reject();\n }\n return result;\n });\n }\n\n self.connectToAddress = function (address, options) {\n if (!address) {\n return Promise.reject();\n }\n\n address = normalizeAddress(address);\n\n let urls = [];\n\n if (/^[^:]+:\\/\\//.test(address)) {\n // Protocol specified - connect as is\n urls.push(address);\n } else {\n urls.push(`https://${address}`);\n urls.push(`http://${address}`);\n }\n\n let i = 0;\n\n function onFail() {\n console.log(`connectToAddress ${urls[i]} failed`);\n\n if (++i < urls.length) {\n return tryConnectToAddress(urls[i], options).catch(onFail);\n }\n\n return Promise.resolve({\n State: 'Unavailable'\n });\n }\n\n return tryConnectToAddress(urls[i], options).catch(onFail);\n };\n\n self.deleteServer = (serverId) => {\n if (!serverId) {\n throw new Error('null serverId');\n }\n\n let server = credentialProvider.credentials().Servers.filter((s) => s.Id === serverId);\n server = server.length ? server[0] : null;\n\n return new Promise((resolve, reject) => {\n function onDone() {\n const credentials = credentialProvider.credentials();\n\n credentials.Servers = credentials.Servers.filter((s) => s.Id !== serverId);\n\n credentialProvider.credentials(credentials);\n resolve();\n }\n\n if (!server.ConnectServerId) {\n onDone();\n return;\n }\n });\n };\n }\n\n connect(options) {\n console.log('Begin connect');\n\n return this.getAvailableServers().then((servers) => {\n return this.connectToServers(servers, options);\n });\n }\n\n handleMessageReceived(msg) {\n const serverId = msg.ServerId;\n if (serverId) {\n const apiClient = this.getApiClient(serverId);\n if (apiClient) {\n if (typeof msg.Data === 'string') {\n try {\n msg.Data = JSON.parse(msg.Data);\n } catch (err) {\n console.log('unable to parse json content: ' + err);\n }\n }\n\n apiClient.handleMessageReceived(msg);\n }\n }\n }\n\n getApiClients() {\n const servers = this.getSavedServers();\n\n for (let i = 0, length = servers.length; i < length; i++) {\n const server = servers[i];\n if (server.Id) {\n this._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode));\n }\n }\n\n return this._apiClients;\n }\n\n getApiClient(item) {\n if (!item) {\n throw new Error('item or serverId cannot be null');\n }\n\n // Accept string + object\n if (item.ServerId) {\n item = item.ServerId;\n }\n\n return this._apiClients.filter((a) => {\n const serverInfo = a.serverInfo();\n\n // We have to keep this hack in here because of the addApiClient method\n return !serverInfo || serverInfo.Id === item;\n })[0];\n }\n\n minServerVersion(val) {\n if (val) {\n this._minServerVersion = val;\n }\n\n return this._minServerVersion;\n }\n}\n","import events from './events';\nimport appStorage from './appStorage';\n\nfunction ensure(instance, data) {\n if (!instance._credentials) {\n const json = instance.appStorage.getItem(instance.key) || '{}';\n\n console.log(`credentials initialized with: ${json}`);\n instance._credentials = JSON.parse(json);\n instance._credentials.Servers = instance._credentials.Servers || [];\n }\n}\n\nfunction set(instance, data) {\n if (data) {\n instance._credentials = data;\n instance.appStorage.setItem(instance.key, JSON.stringify(data));\n } else {\n instance.clear();\n }\n\n events.trigger(instance, 'credentialsupdated');\n}\n\nexport default class Credentials {\n constructor(key) {\n this.key = key || 'jellyfin_credentials';\n this.appStorage = appStorage;\n }\n\n clear() {\n this._credentials = null;\n this.appStorage.removeItem(this.key);\n }\n\n credentials(data) {\n if (data) {\n set(this, data);\n }\n\n ensure(this);\n return this._credentials;\n }\n\n addOrUpdateServer(list, server) {\n if (!server.Id) {\n throw new Error('Server.Id cannot be null or empty');\n }\n\n const existing = list.filter(({ Id }) => Id === server.Id)[0];\n\n if (existing) {\n // Merge the data\n existing.DateLastAccessed = Math.max(existing.DateLastAccessed || 0, server.DateLastAccessed || 0);\n\n existing.UserLinkType = server.UserLinkType;\n\n if (server.AccessToken) {\n existing.AccessToken = server.AccessToken;\n existing.UserId = server.UserId;\n }\n if (server.ExchangeToken) {\n existing.ExchangeToken = server.ExchangeToken;\n }\n if (server.RemoteAddress) {\n existing.RemoteAddress = server.RemoteAddress;\n }\n if (server.ManualAddress) {\n existing.ManualAddress = server.ManualAddress;\n }\n if (server.LocalAddress) {\n existing.LocalAddress = server.LocalAddress;\n }\n if (server.Name) {\n existing.Name = server.Name;\n }\n if (server.LastConnectionMode != null) {\n existing.LastConnectionMode = server.LastConnectionMode;\n }\n if (server.ConnectServerId) {\n existing.ConnectServerId = server.ConnectServerId;\n }\n\n return existing;\n } else {\n list.push(server);\n return server;\n }\n }\n}\n","import ApiClient from './apiClient';\nimport ApiClientCore from './apiClientCore';\nimport AppStorage from './appStorage';\nimport ConnectionManager from './connectionManager';\nimport Credentials from './credentials';\nimport Events from './events';\n\nexport default {\n ApiClient,\n ApiClientCore,\n AppStorage,\n ConnectionManager,\n Credentials,\n Events\n};\n"],"sourceRoot":""} +\ No newline at end of file diff --git a/src/playbackmanager.js b/src/playbackmanager.js index 3aa6f05..939c2ba 100644 --- a/src/playbackmanager.js +++ b/src/playbackmanager.js @@ -132,7 +132,8 @@ function getProgressPayload () { PlaylistItemId: getPlaylistItemId(), PositionTicks: getPostitionTicks(), RepeatMode: getRepeatMode(), - VolumeLevel: getVolumeLevel() + VolumeLevel: getVolumeLevel(), + EventName: "pauseplayupdate" }; return payload; }