├── LICENSE ├── README.md ├── docker ├── Dockerfile ├── README.md └── build.sh ├── patches └── PluginSecurityManager.cs.patch └── replacements └── connectionmanager.js /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {{description}} 294 | Copyright (C) {{year}} {{fullname}} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **Update 2019-01-17** - The Emby team has deleted the relevant GitHub isuses in an attempt to hide community backlash. 2 | Below are archived versions of those issues: 3 | 4 | - [Issue 3075 - GPL Violation](https://web.archive.org/web/20181212044938/https://github.com/MediaBrowser/Emby/issues/3075) 5 | - [Issue 3479 - Source Code Missing (Going Proprietary)](https://web.archive.org/web/20181212100152/https://github.com/MediaBrowser/Emby/issues/3479) 6 | 7 | **Announcing Jellyfin - An Emby Fork!** - I am now focusing my time on [Jellyfin: a free software fork of Emby](https://github.com/jellyfin/jellyfin). 8 | For those using the emby-unlocked image - a drop-in replacement of Jellyfin is available on [Docker Hub](https://hub.docker.com/r/jellyfin/jellyfin/) as `jellyfin/jellyfin`. 9 | 10 | ⚠ This was spurred from Emby's decision to move to a **proprietary license** [[source]](https://web.archive.org/web/20181212100152/https://github.com/MediaBrowser/Emby/issues/3479). ⚠ 11 | 12 | 13 | --- 14 | 15 | 16 | # emby-unlocked 17 | Emby with the premium Emby Premiere features unlocked. 18 | 19 | **Note** - I'm not going to be devoting much time to into maintaining emby-unlocked from here on. 20 | The upstream project is too hostile towards the free software community. 21 | 22 | * reliance upon proprietary blobs 23 | * missing build scripts 24 | * missing licenses 25 | * missing source code 26 | 27 | ## Releases 28 | Releases including the patch are available below: 29 | 30 | - [Arch Linux](https://aur.archlinux.org/packages/emby-server-unlocked/) 31 | - [Docker](https://hub.docker.com/r/nvllsvm/emby-unlocked/) 32 | 33 | ## Help! Premiere feature x does not work. 34 | While this patch makes your local server believe Emby Premiere features are unlocked, some features may still not function. 35 | For example, both tv.emby.media and the mobile apps rely upon validation with the Emby-owned mb3admin.com server. 36 | 37 | ## Modifications 38 | 39 | [PluginSecurityManager.cs.patch](https://github.com/nvllsvm/emby-unlocked/blob/master/patches/PluginSecurityManager.cs.patch) 40 | 41 | Before compilation, simply patch the existing file: 42 | ``` 43 | patch -N -p1 -r - Emby.Server.Implementations/Security/PluginSecurityManager.cs < ../PluginSecurityManager.cs.patch 44 | ``` 45 | 46 | [connectionmanager.js](https://github.com/nvllsvm/emby-unlocked/blob/master/replacements/connectionmanager.js) 47 | 48 | Not really sure what this unlocks outside of removing the nag on the **Sync** screen. 49 | Sync doesn't seem to work afterwards. 50 | Regardless - your own Emby server has zero need to contact the Emby-owned validation URL: [https://mb3admin.com/admin/service/registration/validateDevice](https://mb3admin.com/admin/service/registration/validateDevice). 51 | 52 | The included version of this in the source distribution is minified. Thus, making a patch is difficult. 53 | The only difference boils down to replacing ``self.getRegistrationInfo`` with this: 54 | 55 | ``` 56 | self.getRegistrationInfo = function (feature, apiClient, options) { 57 | var cacheKey = getCacheKey(feature, apiClient, options); 58 | appStorage.setItem(cacheKey, JSON.stringify({ 59 | lastValidDate: new Date().getTime(), 60 | deviceId: self.deviceId() 61 | })); 62 | return Promise.resolve(); 63 | }; 64 | ``` 65 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:edge 2 | 3 | COPY build.sh /build.sh 4 | RUN /build.sh 5 | 6 | EXPOSE 8096 7 | 8 | ENV PUID=1000 PGID=1000 9 | 10 | VOLUME /config /media 11 | 12 | HEALTHCHECK CMD wget -q http://localhost:8096/swagger -O /dev/null || exit 1 13 | 14 | ENTRYPOINT \ 15 | chown $PUID:$PGID /config /media && \ 16 | su-exec $PUID:$PGID \ 17 | mono /emby/MediaBrowser.Server.Mono.exe \ 18 | -programdata /config \ 19 | -ffmpeg /usr/bin/ffmpeg \ 20 | -ffprobe /usr/bin/ffprobe 21 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | Emby with Emby Premiere features unlocked. 2 | 3 | # Environment Variables 4 | 5 | **Optional** 6 | - ``PUID`` - User ID to run as (default 1000) 7 | - ``PGID`` - Group ID to run as (default 1000) 8 | 9 | # Volumes 10 | 11 | - ``/config`` - Emby configuration 12 | - ``/media`` - Media 13 | 14 | # Ports 15 | 16 | - ``8096`` - API and web UI 17 | 18 | # Usage 19 | 20 | ``` 21 | $ docker run \ 22 | -e PUID=1000 \ 23 | -e PGID=1000 \ 24 | -p 8096:8096 \ 25 | -v /host/config:/config \ 26 | -v /host/media:/media \ 27 | nvllsvm/emby-unlocked 28 | ``` 29 | -------------------------------------------------------------------------------- /docker/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ex 3 | 4 | BUILD_DIR=/build 5 | EMBY_DIR=/emby 6 | 7 | install_dependencies() { 8 | # Testing repo for Mono 9 | echo http://dl-cdn.alpinelinux.org/alpine/edge/testing >> /etc/apk/repositories 10 | 11 | apk upgrade 12 | 13 | # Build deps 14 | apk add -t .dev git ffmpeg-dev mono-dev binutils curl icu libunwind openssl bash zip 15 | 16 | # Run deps 17 | apk add ffmpeg mono sqlite imagemagick-dev sqlite-dev su-exec 18 | } 19 | 20 | 21 | cleanup_dependencies() { 22 | apk del --purge -t .dev 23 | } 24 | 25 | 26 | build_msbuild() { 27 | git clone --depth 1 https://github.com/Microsoft/msbuild.git 28 | set +e 29 | ./msbuild/build/build.sh 30 | ./msbuild/build/build.sh -hostType mono 31 | set -e 32 | } 33 | 34 | 35 | build_emby() { 36 | git clone --depth 1 https://github.com/MediaBrowser/Emby 37 | git clone --depth 1 https://github.com/nvllsvm/emby-unlocked 38 | 39 | patch -N -p1 \ 40 | Emby/Emby.Server.Implementations/Security/PluginSecurityManager.cs \ 41 | emby-unlocked/patches/PluginSecurityManager.cs.patch 42 | 43 | msbuild/artifacts/mono-msbuild/msbuild \ 44 | /p:Configuration='Release Mono' \ 45 | /p:Platform='Any CPU' \ 46 | /p:OutputPath="$EMBY_DIR" \ 47 | /t:build Emby/MediaBrowser.sln 48 | mono --aot='full' -O='all' "$EMBY_DIR"/MediaBrowser.Server.Mono.exe 49 | 50 | cp emby-unlocked/replacements/connectionmanager.js \ 51 | /emby/dashboard-ui/bower_components/emby-apiclient 52 | } 53 | 54 | 55 | mkdir -p "$EMBY_DIR" "$BUILD_DIR" 56 | install_dependencies 57 | 58 | cd "$BUILD_DIR" 59 | build_msbuild 60 | build_emby 61 | cd 62 | 63 | cleanup_dependencies 64 | rm -rf "$BUILD_DIR" /build.sh /root /tmp /var/cache/apk/* 65 | mkdir /root /tmp 66 | -------------------------------------------------------------------------------- /patches/PluginSecurityManager.cs.patch: -------------------------------------------------------------------------------- 1 | *** a 2018-07-21 23:56:29.695217076 -0400 2 | --- b 2018-07-21 23:40:56.679819085 -0400 3 | *************** 4 | *** 188,296 **** 5 | 6 | private async Task GetRegistrationStatusInternal(string feature, bool forceCallToServer, string version, CancellationToken cancellationToken) 7 | { 8 | ! await _regCheckLock.WaitAsync(cancellationToken).ConfigureAwait(false); 9 | ! 10 | ! try 11 | ! { 12 | ! var regInfo = LicenseFile.GetRegInfo(feature); 13 | ! var lastChecked = regInfo == null ? DateTime.MinValue : regInfo.LastChecked; 14 | ! var expDate = regInfo == null ? DateTime.MinValue : regInfo.ExpirationDate; 15 | ! 16 | ! var maxCacheDays = 14; 17 | ! var nextCheckDate = new[] { expDate, lastChecked.AddDays(maxCacheDays) }.Min(); 18 | ! 19 | ! if (nextCheckDate > DateTime.UtcNow.AddDays(maxCacheDays)) 20 | ! { 21 | ! nextCheckDate = DateTime.MinValue; 22 | ! } 23 | ! 24 | ! //check the reg file first to alleviate strain on the MB admin server - must actually check in every 30 days tho 25 | ! var reg = new RegRecord 26 | ! { 27 | ! // Cache the result for up to a week 28 | ! registered = regInfo != null && nextCheckDate >= DateTime.UtcNow && expDate >= DateTime.UtcNow, 29 | ! expDate = expDate 30 | ! }; 31 | ! 32 | ! var key = SupporterKey; 33 | ! 34 | ! if (!forceCallToServer && string.IsNullOrWhiteSpace(key)) 35 | ! { 36 | ! return new MBRegistrationRecord(); 37 | ! } 38 | ! 39 | ! var success = reg.registered; 40 | ! 41 | ! if (!(lastChecked > DateTime.UtcNow.AddDays(-1)) || (!reg.registered)) 42 | ! { 43 | ! var data = new Dictionary 44 | ! { 45 | ! { "feature", feature }, 46 | ! { "key", key }, 47 | ! { "mac", _appHost.SystemId }, 48 | ! { "systemid", _appHost.SystemId }, 49 | ! { "ver", version }, 50 | ! { "platform", _appHost.OperatingSystemDisplayName } 51 | ! }; 52 | ! 53 | ! try 54 | ! { 55 | ! var options = new HttpRequestOptions 56 | ! { 57 | ! Url = MBValidateUrl, 58 | ! 59 | ! // Seeing block length errors 60 | ! EnableHttpCompression = false, 61 | ! BufferContent = false, 62 | ! CancellationToken = cancellationToken 63 | ! }; 64 | ! 65 | ! options.SetPostData(data); 66 | ! 67 | ! using (var response = (await _httpClient.Post(options).ConfigureAwait(false))) 68 | ! { 69 | ! using (var json = response.Content) 70 | ! { 71 | ! reg = await _jsonSerializer.DeserializeFromStreamAsync(json).ConfigureAwait(false); 72 | ! success = true; 73 | ! } 74 | ! } 75 | ! 76 | ! if (reg.registered) 77 | ! { 78 | ! _logger.Info("Registered for feature {0}", feature); 79 | ! LicenseFile.AddRegCheck(feature, reg.expDate); 80 | ! } 81 | ! else 82 | ! { 83 | ! _logger.Info("Not registered for feature {0}", feature); 84 | ! LicenseFile.RemoveRegCheck(feature); 85 | ! } 86 | ! 87 | ! } 88 | ! catch (Exception e) 89 | ! { 90 | ! _logger.ErrorException("Error checking registration status of {0}", e, feature); 91 | ! } 92 | ! } 93 | ! 94 | ! var record = new MBRegistrationRecord 95 | ! { 96 | ! IsRegistered = reg.registered, 97 | ! ExpirationDate = reg.expDate, 98 | ! RegChecked = true, 99 | ! RegError = !success 100 | ! }; 101 | ! 102 | ! record.TrialVersion = IsInTrial(reg.expDate, record.RegChecked, record.IsRegistered); 103 | ! record.IsValid = !record.RegChecked || record.IsRegistered || record.TrialVersion; 104 | ! 105 | ! return record; 106 | ! } 107 | ! finally 108 | { 109 | ! _regCheckLock.Release(); 110 | ! } 111 | } 112 | 113 | private bool IsInTrial(DateTime expirationDate, bool regChecked, bool isRegistered) 114 | --- 188,201 ---- 115 | 116 | private async Task GetRegistrationStatusInternal(string feature, bool forceCallToServer, string version, CancellationToken cancellationToken) 117 | { 118 | ! return new MBRegistrationRecord 119 | { 120 | ! IsRegistered = true, 121 | ! RegChecked = true, 122 | ! RegError = false, 123 | ! TrialVersion = false, 124 | ! IsValid = true 125 | ! }; 126 | } 127 | 128 | private bool IsInTrial(DateTime expirationDate, bool regChecked, bool isRegistered) 129 | *************** 130 | *** 306,309 **** 131 | return isInTrial && !isRegistered; 132 | } 133 | } 134 | ! } 135 | \ No newline at end of file 136 | --- 211,214 ---- 137 | return isInTrial && !isRegistered; 138 | } 139 | } 140 | ! } 141 | -------------------------------------------------------------------------------- /replacements/connectionmanager.js: -------------------------------------------------------------------------------- 1 | define(["events", "apiclient", "appStorage"], function(events, apiClientFactory, appStorage) { 2 | "use strict"; 3 | 4 | function getServerAddress(server, mode) { 5 | switch (mode) { 6 | case ConnectionMode.Local: 7 | return server.LocalAddress; 8 | case ConnectionMode.Manual: 9 | return server.ManualAddress; 10 | case ConnectionMode.Remote: 11 | return server.RemoteAddress; 12 | default: 13 | return server.ManualAddress || server.LocalAddress || server.RemoteAddress 14 | } 15 | } 16 | 17 | function paramsToString(params) { 18 | var values = []; 19 | for (var key in params) { 20 | var value = params[key]; 21 | null !== value && void 0 !== value && "" !== value && values.push(encodeURIComponent(key) + "=" + encodeURIComponent(value)) 22 | } 23 | return values.join("&") 24 | } 25 | 26 | function resolveFailure(instance, resolve) { 27 | resolve({ 28 | State: "Unavailable", 29 | ConnectUser: instance.connectUser() 30 | }) 31 | } 32 | 33 | function mergeServers(credentialProvider, list1, list2) { 34 | for (var i = 0, length = list2.length; i < length; i++) credentialProvider.addOrUpdateServer(list1, list2[i]); 35 | return list1 36 | } 37 | 38 | function updateServerInfo(server, systemInfo) { 39 | server.Name = systemInfo.ServerName, systemInfo.Id && (server.Id = systemInfo.Id), systemInfo.LocalAddress && (server.LocalAddress = systemInfo.LocalAddress), systemInfo.WanAddress && (server.RemoteAddress = systemInfo.WanAddress) 40 | } 41 | 42 | function getEmbyServerUrl(baseUrl, handler) { 43 | return baseUrl + "/emby/" + handler 44 | } 45 | 46 | function getFetchPromise(request) { 47 | var headers = request.headers || {}; 48 | "json" === request.dataType && (headers.accept = "application/json"); 49 | var fetchRequest = { 50 | headers: headers, 51 | method: request.type, 52 | credentials: "same-origin" 53 | }, 54 | contentType = request.contentType; 55 | return request.data && ("string" == typeof request.data ? fetchRequest.body = request.data : (fetchRequest.body = paramsToString(request.data), contentType = contentType || "application/x-www-form-urlencoded; charset=UTF-8")), contentType && (headers["Content-Type"] = contentType), request.timeout ? fetchWithTimeout(request.url, fetchRequest, request.timeout) : fetch(request.url, fetchRequest) 56 | } 57 | 58 | function fetchWithTimeout(url, options, timeoutMs) { 59 | return console.log("fetchWithTimeout: timeoutMs: " + timeoutMs + ", url: " + url), new Promise(function(resolve, reject) { 60 | var timeout = setTimeout(reject, timeoutMs); 61 | options = options || {}, options.credentials = "same-origin", fetch(url, options).then(function(response) { 62 | clearTimeout(timeout), console.log("fetchWithTimeout: succeeded connecting to url: " + url), resolve(response) 63 | }, function(error) { 64 | clearTimeout(timeout), console.log("fetchWithTimeout: timed out connecting to url: " + url), reject() 65 | }) 66 | }) 67 | } 68 | 69 | function ajax(request) { 70 | if (!request) throw new Error("Request cannot be null"); 71 | return request.headers = request.headers || {}, console.log("ConnectionManager requesting url: " + request.url), getFetchPromise(request).then(function(response) { 72 | return console.log("ConnectionManager response status: " + response.status + ", url: " + request.url), response.status < 400 ? "json" === request.dataType || "application/json" === request.headers.accept ? response.json() : response : Promise.reject(response) 73 | }, function(err) { 74 | throw console.log("ConnectionManager request failed to url: " + request.url), err 75 | }) 76 | } 77 | 78 | function getConnectUrl(handler) { 79 | return "https://connect.emby.media/service/" + handler 80 | } 81 | 82 | function replaceAll(originalString, strReplace, strWith) { 83 | var reg = new RegExp(strReplace, "ig"); 84 | return originalString.replace(reg, strWith) 85 | } 86 | 87 | function normalizeAddress(address) { 88 | return address = address.trim(), 0 !== address.toLowerCase().indexOf("http") && (address = "http://" + address), address = replaceAll(address, "Http:", "http:"), address = replaceAll(address, "Https:", "https:") 89 | } 90 | 91 | function stringEqualsIgnoreCase(str1, str2) { 92 | return (str1 || "").toLowerCase() === (str2 || "").toLowerCase() 93 | } 94 | 95 | function compareVersions(a, b) { 96 | a = a.split("."), b = b.split("."); 97 | for (var i = 0, length = Math.max(a.length, b.length); i < length; i++) { 98 | var aVal = parseInt(a[i] || "0"), 99 | bVal = parseInt(b[i] || "0"); 100 | if (aVal < bVal) return -1; 101 | if (aVal > bVal) return 1 102 | } 103 | return 0 104 | } 105 | var defaultTimeout = 2e4, 106 | ConnectionMode = { 107 | Local: 0, 108 | Remote: 1, 109 | Manual: 2 110 | }, 111 | ConnectionManager = function(credentialProvider, appName, appVersion, deviceName, deviceId, capabilities, devicePixelRatio) { 112 | function onConnectUserSignIn(user) { 113 | connectUser = user, events.trigger(self, "connectusersignedin", [user]) 114 | } 115 | 116 | function onAuthenticated(apiClient, result, options, saveCredentials) { 117 | var credentials = credentialProvider.credentials(), 118 | servers = credentials.Servers.filter(function(s) { 119 | return s.Id === result.ServerId 120 | }), 121 | server = servers.length ? servers[0] : apiClient.serverInfo(); 122 | return !1 !== options.updateDateLastAccessed && (server.DateLastAccessed = (new Date).getTime()), server.Id = result.ServerId, saveCredentials ? (server.UserId = result.User.Id, server.AccessToken = result.AccessToken) : (server.UserId = null, server.AccessToken = null), credentialProvider.addOrUpdateServer(credentials.Servers, server), credentialProvider.credentials(credentials), apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection, apiClient.serverInfo(server), afterConnected(apiClient, options), onLocalUserSignIn(server, apiClient.serverAddress(), result.User) 123 | } 124 | 125 | function afterConnected(apiClient, options) { 126 | options = options || {}, !1 !== options.reportCapabilities && apiClient.reportCapabilities(capabilities), apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection, !1 !== options.enableWebSocket && (console.log("calling apiClient.ensureWebSocket"), apiClient.ensureWebSocket()) 127 | } 128 | 129 | function onLocalUserSignIn(server, serverUrl, user) { 130 | return self._getOrAddApiClient(server, serverUrl), (self.onLocalUserSignedIn ? self.onLocalUserSignedIn.call(self, user) : Promise.resolve()).then(function() { 131 | events.trigger(self, "localusersignedin", [user]) 132 | }) 133 | } 134 | 135 | function ensureConnectUser(credentials) { 136 | return connectUser && connectUser.Id === credentials.ConnectUserId ? Promise.resolve() : credentials.ConnectUserId && credentials.ConnectAccessToken ? (connectUser = null, getConnectUser(credentials.ConnectUserId, credentials.ConnectAccessToken).then(function(user) { 137 | return onConnectUserSignIn(user), Promise.resolve() 138 | }, function() { 139 | return Promise.resolve() 140 | })) : Promise.resolve() 141 | } 142 | 143 | function getConnectUser(userId, accessToken) { 144 | if (!userId) throw new Error("null userId"); 145 | if (!accessToken) throw new Error("null accessToken"); 146 | return ajax({ 147 | type: "GET", 148 | url: "https://connect.emby.media/service/user?id=" + userId, 149 | dataType: "json", 150 | headers: { 151 | "X-Application": appName + "/" + appVersion, 152 | "X-Connect-UserToken": accessToken 153 | } 154 | }) 155 | } 156 | 157 | function addAuthenticationInfoFromConnect(server, serverUrl, credentials) { 158 | if (!server.ExchangeToken) throw new Error("server.ExchangeToken cannot be null"); 159 | if (!credentials.ConnectUserId) throw new Error("credentials.ConnectUserId cannot be null"); 160 | var url = getEmbyServerUrl(serverUrl, "Connect/Exchange?format=json&ConnectUserId=" + credentials.ConnectUserId), 161 | auth = 'MediaBrowser Client="' + appName + '", Device="' + deviceName + '", DeviceId="' + deviceId + '", Version="' + appVersion + '"'; 162 | return ajax({ 163 | type: "GET", 164 | url: url, 165 | dataType: "json", 166 | headers: { 167 | "X-MediaBrowser-Token": server.ExchangeToken, 168 | "X-Emby-Authorization": auth 169 | } 170 | }).then(function(auth) { 171 | return server.UserId = auth.LocalUserId, server.AccessToken = auth.AccessToken, auth 172 | }, function() { 173 | return server.UserId = null, server.AccessToken = null, Promise.reject() 174 | }) 175 | } 176 | 177 | function validateAuthentication(server, serverUrl) { 178 | return ajax({ 179 | type: "GET", 180 | url: getEmbyServerUrl(serverUrl, "System/Info"), 181 | dataType: "json", 182 | headers: { 183 | "X-MediaBrowser-Token": server.AccessToken 184 | } 185 | }).then(function(systemInfo) { 186 | return updateServerInfo(server, systemInfo), Promise.resolve() 187 | }, function() { 188 | return server.UserId = null, server.AccessToken = null, Promise.resolve() 189 | }) 190 | } 191 | 192 | function getImageUrl(localUser) { 193 | if (connectUser && connectUser.ImageUrl) return { 194 | url: connectUser.ImageUrl 195 | }; 196 | if (localUser && localUser.PrimaryImageTag) { 197 | return { 198 | url: self.getApiClient(localUser).getUserImageUrl(localUser.Id, { 199 | tag: localUser.PrimaryImageTag, 200 | type: "Primary" 201 | }), 202 | supportsParams: !0 203 | } 204 | } 205 | return { 206 | url: null, 207 | supportsParams: !1 208 | } 209 | } 210 | 211 | function logoutOfServer(apiClient) { 212 | var serverInfo = apiClient.serverInfo() || {}, 213 | logoutInfo = { 214 | serverId: serverInfo.Id 215 | }; 216 | return apiClient.logout().then(function() { 217 | events.trigger(self, "localusersignedout", [logoutInfo]) 218 | }, function() { 219 | events.trigger(self, "localusersignedout", [logoutInfo]) 220 | }) 221 | } 222 | 223 | function getConnectServers(credentials) { 224 | return console.log("Begin getConnectServers"), credentials.ConnectAccessToken && credentials.ConnectUserId ? ajax({ 225 | type: "GET", 226 | url: "https://connect.emby.media/service/servers?userId=" + credentials.ConnectUserId, 227 | dataType: "json", 228 | headers: { 229 | "X-Application": appName + "/" + appVersion, 230 | "X-Connect-UserToken": credentials.ConnectAccessToken 231 | } 232 | }).then(function(servers) { 233 | return servers.map(function(i) { 234 | return { 235 | ExchangeToken: i.AccessKey, 236 | ConnectServerId: i.Id, 237 | Id: i.SystemId, 238 | Name: i.Name, 239 | RemoteAddress: i.Url, 240 | LocalAddress: i.LocalAddress, 241 | UserLinkType: "guest" === (i.UserType || "").toLowerCase() ? "Guest" : "LinkedUser" 242 | } 243 | }) 244 | }, function() { 245 | return credentials.Servers.slice(0).filter(function(s) { 246 | return s.ExchangeToken 247 | }) 248 | }) : Promise.resolve([]) 249 | } 250 | 251 | function filterServers(servers, connectServers) { 252 | return servers.filter(function(server) { 253 | return !server.ExchangeToken || connectServers.filter(function(connectServer) { 254 | return server.Id === connectServer.Id 255 | }).length > 0 256 | }) 257 | } 258 | 259 | function findServers() { 260 | return new Promise(function(resolve, reject) { 261 | var onFinish = function(foundServers) { 262 | var servers = foundServers.map(function(foundServer) { 263 | var info = { 264 | Id: foundServer.Id, 265 | LocalAddress: convertEndpointAddressToManualAddress(foundServer) || foundServer.Address, 266 | Name: foundServer.Name 267 | }; 268 | return info.LastConnectionMode = info.ManualAddress ? ConnectionMode.Manual : ConnectionMode.Local, info 269 | }); 270 | resolve(servers) 271 | }; 272 | require(["serverdiscovery"], function(serverDiscovery) { 273 | serverDiscovery.findServers(1e3).then(onFinish, function() { 274 | onFinish([]) 275 | }) 276 | }) 277 | }) 278 | } 279 | 280 | function convertEndpointAddressToManualAddress(info) { 281 | if (info.Address && info.EndpointAddress) { 282 | var address = info.EndpointAddress.split(":")[0], 283 | parts = info.Address.split(":"); 284 | if (parts.length > 1) { 285 | var portString = parts[parts.length - 1]; 286 | isNaN(parseInt(portString)) || (address += ":" + portString) 287 | } 288 | return normalizeAddress(address) 289 | } 290 | return null 291 | } 292 | 293 | function getTryConnectPromise(url, connectionMode, state, resolve, reject) { 294 | console.log("getTryConnectPromise " + url), ajax({ 295 | url: getEmbyServerUrl(url, "system/info/public"), 296 | timeout: defaultTimeout, 297 | type: "GET", 298 | dataType: "json" 299 | }).then(function(result) { 300 | state.resolved || (state.resolved = !0, console.log("Reconnect succeeded to " + url), resolve({ 301 | url: url, 302 | connectionMode: connectionMode, 303 | data: result 304 | })) 305 | }, function() { 306 | state.resolved || (console.log("Reconnect failed to " + url), ++state.rejects >= state.numAddresses && reject()) 307 | }) 308 | } 309 | 310 | function tryReconnect(serverInfo) { 311 | var addresses = [], 312 | addressesStrings = []; 313 | return !serverInfo.manualAddressOnly && serverInfo.LocalAddress && -1 === addressesStrings.indexOf(serverInfo.LocalAddress) && (addresses.push({ 314 | url: serverInfo.LocalAddress, 315 | mode: ConnectionMode.Local, 316 | timeout: 0 317 | }), addressesStrings.push(addresses[addresses.length - 1].url)), serverInfo.ManualAddress && -1 === addressesStrings.indexOf(serverInfo.ManualAddress) && (addresses.push({ 318 | url: serverInfo.ManualAddress, 319 | mode: ConnectionMode.Manual, 320 | timeout: 100 321 | }), addressesStrings.push(addresses[addresses.length - 1].url)), !serverInfo.manualAddressOnly && serverInfo.RemoteAddress && -1 === addressesStrings.indexOf(serverInfo.RemoteAddress) && (addresses.push({ 322 | url: serverInfo.RemoteAddress, 323 | mode: ConnectionMode.Remote, 324 | timeout: 200 325 | }), addressesStrings.push(addresses[addresses.length - 1].url)), console.log("tryReconnect: " + addressesStrings.join("|")), new Promise(function(resolve, reject) { 326 | var state = {}; 327 | state.numAddresses = addresses.length, state.rejects = 0, addresses.map(function(url) { 328 | setTimeout(function() { 329 | state.resolved || getTryConnectPromise(url.url, url.mode, state, resolve, reject) 330 | }, url.timeout) 331 | }) 332 | }) 333 | } 334 | 335 | function onSuccessfulConnection(server, systemInfo, connectionMode, serverUrl, options, resolve) { 336 | var credentials = credentialProvider.credentials(); 337 | options = options || {}, credentials.ConnectAccessToken && !1 !== options.enableAutoLogin ? ensureConnectUser(credentials).then(function() { 338 | server.ExchangeToken ? addAuthenticationInfoFromConnect(server, serverUrl, credentials).then(function() { 339 | afterConnectValidated(server, credentials, systemInfo, connectionMode, serverUrl, !0, options, resolve) 340 | }, function() { 341 | afterConnectValidated(server, credentials, systemInfo, connectionMode, serverUrl, !0, options, resolve) 342 | }) : afterConnectValidated(server, credentials, systemInfo, connectionMode, serverUrl, !0, options, resolve) 343 | }) : afterConnectValidated(server, credentials, systemInfo, connectionMode, serverUrl, !0, options, resolve) 344 | } 345 | 346 | function afterConnectValidated(server, credentials, systemInfo, connectionMode, serverUrl, verifyLocalAuthentication, options, resolve) { 347 | if (options = options || {}, !1 === options.enableAutoLogin) server.UserId = null, server.AccessToken = null; 348 | else if (verifyLocalAuthentication && server.AccessToken && !1 !== options.enableAutoLogin) return void validateAuthentication(server, serverUrl).then(function() { 349 | afterConnectValidated(server, credentials, systemInfo, connectionMode, serverUrl, !1, options, resolve) 350 | }); 351 | updateServerInfo(server, systemInfo), server.LastConnectionMode = connectionMode, !1 !== options.updateDateLastAccessed && (server.DateLastAccessed = (new Date).getTime()), credentialProvider.addOrUpdateServer(credentials.Servers, server), credentialProvider.credentials(credentials); 352 | var result = { 353 | Servers: [] 354 | }; 355 | result.ApiClient = self._getOrAddApiClient(server, serverUrl), result.ApiClient.setSystemInfo(systemInfo), result.State = server.AccessToken && !1 !== options.enableAutoLogin ? "SignedIn" : "ServerSignIn", result.Servers.push(server), result.ApiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection, result.ApiClient.updateServerInfo(server, serverUrl); 356 | var resolveActions = function() { 357 | resolve(result), events.trigger(self, "connected", [result]) 358 | }; 359 | "SignedIn" === result.State ? (afterConnected(result.ApiClient, options), result.ApiClient.getCurrentUser().then(function(user) { 360 | onLocalUserSignIn(server, serverUrl, user).then(resolveActions, resolveActions) 361 | }, resolveActions)) : resolveActions() 362 | } 363 | 364 | function getCacheKey(feature, apiClient, options) { 365 | options = options || {}; 366 | var viewOnly = options.viewOnly, 367 | cacheKey = "regInfo-" + apiClient.serverId(); 368 | return viewOnly && (cacheKey += "-viewonly"), cacheKey 369 | } 370 | 371 | function addAppInfoToConnectRequest(request) { 372 | request.headers = request.headers || {}, request.headers["X-Application"] = appName + "/" + appVersion 373 | } 374 | 375 | function exchangePin(pinInfo) { 376 | if (!pinInfo) throw new Error("pinInfo cannot be null"); 377 | var request = { 378 | type: "POST", 379 | url: getConnectUrl("pin/authenticate"), 380 | data: { 381 | deviceId: pinInfo.DeviceId, 382 | pin: pinInfo.Pin 383 | }, 384 | dataType: "json" 385 | }; 386 | return addAppInfoToConnectRequest(request), ajax(request) 387 | } 388 | console.log("Begin ConnectionManager constructor"); 389 | var self = this; 390 | this._apiClients = []; 391 | var connectUser; 392 | self.connectUser = function() { 393 | return connectUser 394 | }, self._minServerVersion = "3.2.33", self.appVersion = function() { 395 | return appVersion 396 | }, self.appName = function() { 397 | return appName 398 | }, self.capabilities = function() { 399 | return capabilities 400 | }, self.deviceId = function() { 401 | return deviceId 402 | }, self.credentialProvider = function() { 403 | return credentialProvider 404 | }, self.connectUserId = function() { 405 | return credentialProvider.credentials().ConnectUserId 406 | }, self.connectToken = function() { 407 | return credentialProvider.credentials().ConnectAccessToken 408 | }, self.getServerInfo = function(id) { 409 | return credentialProvider.credentials().Servers.filter(function(s) { 410 | return s.Id === id 411 | })[0] 412 | }, self.getLastUsedServer = function() { 413 | var servers = credentialProvider.credentials().Servers; 414 | return servers.sort(function(a, b) { 415 | return (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0) 416 | }), servers.length ? servers[0] : null 417 | }, self.addApiClient = function(apiClient) { 418 | self._apiClients.push(apiClient); 419 | var existingServers = credentialProvider.credentials().Servers.filter(function(s) { 420 | return stringEqualsIgnoreCase(s.ManualAddress, apiClient.serverAddress()) || stringEqualsIgnoreCase(s.LocalAddress, apiClient.serverAddress()) || stringEqualsIgnoreCase(s.RemoteAddress, apiClient.serverAddress()) 421 | }), 422 | existingServer = existingServers.length ? existingServers[0] : apiClient.serverInfo(); 423 | if (existingServer.DateLastAccessed = (new Date).getTime(), existingServer.LastConnectionMode = ConnectionMode.Manual, existingServer.ManualAddress = apiClient.serverAddress(), apiClient.manualAddressOnly && (existingServer.manualAddressOnly = !0), apiClient.serverInfo(existingServer), apiClient.onAuthenticated = function(instance, result) { 424 | return onAuthenticated(instance, result, {}, !0) 425 | }, !existingServers.length) { 426 | var credentials = credentialProvider.credentials(); 427 | credentials.Servers = [existingServer], credentialProvider.credentials(credentials) 428 | } 429 | events.trigger(self, "apiclientcreated", [apiClient]) 430 | }, self.clearData = function() { 431 | console.log("connection manager clearing data"), connectUser = null; 432 | var credentials = credentialProvider.credentials(); 433 | credentials.ConnectAccessToken = null, credentials.ConnectUserId = null, credentials.Servers = [], credentialProvider.credentials(credentials) 434 | }, self._getOrAddApiClient = function(server, serverUrl) { 435 | var apiClient = self.getApiClient(server.Id); 436 | return apiClient || (apiClient = new apiClientFactory(serverUrl, appName, appVersion, deviceName, deviceId, devicePixelRatio), self._apiClients.push(apiClient), apiClient.serverInfo(server), apiClient.onAuthenticated = function(instance, result) { 437 | return onAuthenticated(instance, result, {}, !0) 438 | }, events.trigger(self, "apiclientcreated", [apiClient])), console.log("returning instance from getOrAddApiClient"), apiClient 439 | }, self.getOrCreateApiClient = function(serverId) { 440 | var credentials = credentialProvider.credentials(), 441 | servers = credentials.Servers.filter(function(s) { 442 | return stringEqualsIgnoreCase(s.Id, serverId) 443 | }); 444 | if (!servers.length) throw new Error("Server not found: " + serverId); 445 | var server = servers[0]; 446 | return self._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode)) 447 | }, self.user = function(apiClient) { 448 | return new Promise(function(resolve, reject) { 449 | function onLocalUserDone(e) { 450 | var image = getImageUrl(localUser); 451 | resolve({ 452 | localUser: localUser, 453 | name: connectUser ? connectUser.Name : localUser ? localUser.Name : null, 454 | imageUrl: image.url, 455 | supportsImageParams: image.supportsParams, 456 | connectUser: connectUser 457 | }) 458 | } 459 | 460 | function onEnsureConnectUserDone() { 461 | apiClient && apiClient.getCurrentUserId() ? apiClient.getCurrentUser().then(function(u) { 462 | localUser = u, onLocalUserDone() 463 | }, onLocalUserDone) : onLocalUserDone() 464 | } 465 | var localUser, credentials = credentialProvider.credentials(); 466 | !credentials.ConnectUserId || !credentials.ConnectAccessToken || apiClient && apiClient.getCurrentUserId() ? onEnsureConnectUserDone() : ensureConnectUser(credentials).then(onEnsureConnectUserDone, onEnsureConnectUserDone) 467 | }) 468 | }, self.logout = function() { 469 | console.log("begin connectionManager loguot"); 470 | for (var promises = [], i = 0, length = self._apiClients.length; i < length; i++) { 471 | var apiClient = self._apiClients[i]; 472 | apiClient.accessToken() && promises.push(logoutOfServer(apiClient)) 473 | } 474 | return Promise.all(promises).then(function() { 475 | for (var credentials = credentialProvider.credentials(), servers = credentials.Servers.filter(function(u) { 476 | return "Guest" !== u.UserLinkType 477 | }), j = 0, numServers = servers.length; j < numServers; j++) { 478 | var server = servers[j]; 479 | server.UserId = null, server.AccessToken = null, server.ExchangeToken = null 480 | } 481 | credentials.Servers = servers, credentials.ConnectAccessToken = null, credentials.ConnectUserId = null, credentialProvider.credentials(credentials), connectUser && (connectUser = null, events.trigger(self, "connectusersignedout")) 482 | }) 483 | }, self.getSavedServers = function() { 484 | var credentials = credentialProvider.credentials(), 485 | servers = credentials.Servers.slice(0); 486 | return servers.sort(function(a, b) { 487 | return (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0) 488 | }), servers 489 | }, self.getAvailableServers = function() { 490 | console.log("Begin getAvailableServers"); 491 | var credentials = credentialProvider.credentials(); 492 | return Promise.all([getConnectServers(credentials), findServers()]).then(function(responses) { 493 | var connectServers = responses[0], 494 | foundServers = responses[1], 495 | servers = credentials.Servers.slice(0); 496 | return mergeServers(credentialProvider, servers, foundServers), mergeServers(credentialProvider, servers, connectServers), servers = filterServers(servers, connectServers), servers.sort(function(a, b) { 497 | return (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0) 498 | }), credentials.Servers = servers, credentialProvider.credentials(credentials), servers 499 | }) 500 | }, self.connectToServers = function(servers, options) { 501 | console.log("Begin connectToServers, with " + servers.length + " servers"); 502 | var firstServer = servers.length ? servers[0] : null; 503 | return firstServer ? self.connectToServer(firstServer, options).then(function(result) { 504 | return "Unavailable" === result.State && (result.State = "ServerSelection"), console.log("resolving connectToServers with result.State: " + result.State), result 505 | }) : Promise.resolve({ 506 | Servers: servers, 507 | State: servers.length || self.connectUser() ? "ServerSelection" : "ConnectSignIn", 508 | ConnectUser: self.connectUser() 509 | }) 510 | }, self.connectToServer = function(server, options) { 511 | return console.log("begin connectToServer"), new Promise(function(resolve, reject) { 512 | options = options || {}, tryReconnect(server).then(function(result) { 513 | var serverUrl = result.url, 514 | connectionMode = result.connectionMode; 515 | result = result.data, 1 === compareVersions(self.minServerVersion(), result.Version) ? (console.log("minServerVersion requirement not met. Server version: " + result.Version), resolve({ 516 | State: "ServerUpdateNeeded", 517 | Servers: [server] 518 | })) : server.Id && result.Id !== server.Id ? (console.log("http request succeeded, but found a different server Id than what was expected"), resolveFailure(self, resolve)) : onSuccessfulConnection(server, result, connectionMode, serverUrl, options, resolve) 519 | }, function() { 520 | resolveFailure(self, resolve) 521 | }) 522 | }) 523 | }, self.connectToAddress = function(address, options) { 524 | function onFail() { 525 | return console.log("connectToAddress " + address + " failed"), Promise.resolve({ 526 | State: "Unavailable", 527 | ConnectUser: instance.connectUser() 528 | }) 529 | } 530 | if (!address) return Promise.reject(); 531 | address = normalizeAddress(address); 532 | var instance = this, 533 | server = { 534 | ManualAddress: address, 535 | LastConnectionMode: ConnectionMode.Manual 536 | }; 537 | return self.connectToServer(server, options).catch(onFail) 538 | }, self.loginToConnect = function(username, password) { 539 | return username && password ? ajax({ 540 | type: "POST", 541 | url: "https://connect.emby.media/service/user/authenticate", 542 | data: { 543 | nameOrEmail: username, 544 | rawpw: password 545 | }, 546 | dataType: "json", 547 | contentType: "application/x-www-form-urlencoded; charset=UTF-8", 548 | headers: { 549 | "X-Application": appName + "/" + appVersion 550 | } 551 | }).then(function(result) { 552 | var credentials = credentialProvider.credentials(); 553 | return credentials.ConnectAccessToken = result.AccessToken, credentials.ConnectUserId = result.User.Id, credentialProvider.credentials(credentials), onConnectUserSignIn(result.User), result 554 | }) : Promise.reject() 555 | }, self.signupForConnect = function(options) { 556 | var email = options.email, 557 | username = options.username, 558 | password = options.password, 559 | passwordConfirm = options.passwordConfirm; 560 | if (!email) return Promise.reject({ 561 | errorCode: "invalidinput" 562 | }); 563 | if (!username) return Promise.reject({ 564 | errorCode: "invalidinput" 565 | }); 566 | if (!password) return Promise.reject({ 567 | errorCode: "invalidinput" 568 | }); 569 | if (!passwordConfirm) return Promise.reject({ 570 | errorCode: "passwordmatch" 571 | }); 572 | if (password !== passwordConfirm) return Promise.reject({ 573 | errorCode: "passwordmatch" 574 | }); 575 | var data = { 576 | email: email, 577 | userName: username, 578 | rawpw: password 579 | }; 580 | return options.grecaptcha && (data.grecaptcha = options.grecaptcha), ajax({ 581 | type: "POST", 582 | url: "https://connect.emby.media/service/register", 583 | data: data, 584 | dataType: "json", 585 | contentType: "application/x-www-form-urlencoded; charset=UTF-8", 586 | headers: { 587 | "X-Application": appName + "/" + appVersion, 588 | "X-CONNECT-TOKEN": "CONNECT-REGISTER" 589 | } 590 | }).catch(function(response) { 591 | try { 592 | return response.json() 593 | } catch (err) { 594 | throw err 595 | } 596 | }).then(function(result) { 597 | if (result && result.Status) return "SUCCESS" === result.Status ? Promise.resolve(result) : Promise.reject({ 598 | errorCode: result.Status 599 | }); 600 | Promise.reject() 601 | }) 602 | }, self.getUserInvitations = function() { 603 | var connectToken = self.connectToken(); 604 | if (!connectToken) throw new Error("null connectToken"); 605 | if (!self.connectUserId()) throw new Error("null connectUserId"); 606 | return ajax({ 607 | type: "GET", 608 | url: "https://connect.emby.media/service/servers?userId=" + self.connectUserId() + "&status=Waiting", 609 | dataType: "json", 610 | headers: { 611 | "X-Connect-UserToken": connectToken, 612 | "X-Application": appName + "/" + appVersion 613 | } 614 | }) 615 | }, self.deleteServer = function(serverId) { 616 | if (!serverId) throw new Error("null serverId"); 617 | var server = credentialProvider.credentials().Servers.filter(function(s) { 618 | return s.Id === serverId 619 | }); 620 | return server = server.length ? server[0] : null, new Promise(function(resolve, reject) { 621 | function onDone() { 622 | var credentials = credentialProvider.credentials(); 623 | credentials.Servers = credentials.Servers.filter(function(s) { 624 | return s.Id !== serverId 625 | }), credentialProvider.credentials(credentials), resolve() 626 | } 627 | if (!server.ConnectServerId) return void onDone(); 628 | var connectToken = self.connectToken(), 629 | connectUserId = self.connectUserId(); 630 | if (!connectToken || !connectUserId) return void onDone(); 631 | ajax({ 632 | type: "DELETE", 633 | url: "https://connect.emby.media/service/serverAuthorizations?serverId=" + server.ConnectServerId + "&userId=" + connectUserId, 634 | headers: { 635 | "X-Connect-UserToken": connectToken, 636 | "X-Application": appName + "/" + appVersion 637 | } 638 | }).then(onDone, onDone) 639 | }) 640 | }, self.rejectServer = function(serverId) { 641 | var connectToken = self.connectToken(); 642 | if (!serverId) throw new Error("null serverId"); 643 | if (!connectToken) throw new Error("null connectToken"); 644 | if (!self.connectUserId()) throw new Error("null connectUserId"); 645 | var url = "https://connect.emby.media/service/serverAuthorizations?serverId=" + serverId + "&userId=" + self.connectUserId(); 646 | return fetch(url, { 647 | method: "DELETE", 648 | headers: { 649 | "X-Connect-UserToken": connectToken, 650 | "X-Application": appName + "/" + appVersion 651 | } 652 | }) 653 | }, self.acceptServer = function(serverId) { 654 | var connectToken = self.connectToken(); 655 | if (!serverId) throw new Error("null serverId"); 656 | if (!connectToken) throw new Error("null connectToken"); 657 | if (!self.connectUserId()) throw new Error("null connectUserId"); 658 | return ajax({ 659 | type: "GET", 660 | url: "https://connect.emby.media/service/ServerAuthorizations/accept?serverId=" + serverId + "&userId=" + self.connectUserId(), 661 | headers: { 662 | "X-Connect-UserToken": connectToken, 663 | "X-Application": appName + "/" + appVersion 664 | } 665 | }) 666 | }, self.resetRegistrationInfo = function(apiClient) { 667 | var cacheKey = getCacheKey("themes", apiClient, { 668 | viewOnly: !0 669 | }); 670 | appStorage.removeItem(cacheKey), cacheKey = getCacheKey("themes", apiClient, { 671 | viewOnly: !1 672 | }), appStorage.removeItem(cacheKey) 673 | }, self.getRegistrationInfo = function(feature, apiClient, options) { 674 | var cacheKey = getCacheKey(feature, apiClient, options); 675 | appStorage.setItem(cacheKey, JSON.stringify({ 676 | lastValidDate: new Date().getTime(), 677 | deviceId: self.deviceId() 678 | })); 679 | return Promise.resolve(); 680 | }, self.createPin = function() { 681 | var request = { 682 | type: "POST", 683 | url: getConnectUrl("pin"), 684 | data: { 685 | deviceId: deviceId 686 | }, 687 | dataType: "json" 688 | }; 689 | return addAppInfoToConnectRequest(request), ajax(request) 690 | }, self.getPinStatus = function(pinInfo) { 691 | if (!pinInfo) throw new Error("pinInfo cannot be null"); 692 | var queryString = { 693 | deviceId: pinInfo.DeviceId, 694 | pin: pinInfo.Pin 695 | }, 696 | request = { 697 | type: "GET", 698 | url: getConnectUrl("pin") + "?" + paramsToString(queryString), 699 | dataType: "json" 700 | }; 701 | return addAppInfoToConnectRequest(request), ajax(request) 702 | }, self.exchangePin = function(pinInfo) { 703 | if (!pinInfo) throw new Error("pinInfo cannot be null"); 704 | return exchangePin(pinInfo).then(function(result) { 705 | var credentials = credentialProvider.credentials(); 706 | return credentials.ConnectAccessToken = result.AccessToken, credentials.ConnectUserId = result.UserId, credentialProvider.credentials(credentials), ensureConnectUser(credentials) 707 | }) 708 | } 709 | }; 710 | return ConnectionManager.prototype.connect = function(options) { 711 | console.log("Begin connect"); 712 | var instance = this; 713 | return instance.getAvailableServers().then(function(servers) { 714 | return instance.connectToServers(servers, options) 715 | }) 716 | }, ConnectionManager.prototype.isLoggedIntoConnect = function() { 717 | return !(!this.connectToken() || !this.connectUserId()) 718 | }, ConnectionManager.prototype.getApiClients = function() { 719 | for (var servers = this.getSavedServers(), i = 0, length = servers.length; i < length; i++) { 720 | var server = servers[i]; 721 | server.Id && this._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode)) 722 | } 723 | return this._apiClients 724 | }, ConnectionManager.prototype.getApiClient = function(item) { 725 | if (!item) throw new Error("item or serverId cannot be null"); 726 | return item.ServerId && (item = item.ServerId), this._apiClients.filter(function(a) { 727 | var serverInfo = a.serverInfo(); 728 | return !serverInfo || serverInfo.Id === item 729 | })[0] 730 | }, ConnectionManager.prototype.minServerVersion = function(val) { 731 | return val && (this._minServerVersion = val), this._minServerVersion 732 | }, ConnectionManager.prototype.handleMessageReceived = function(msg) { 733 | var serverId = msg.ServerId; 734 | if (serverId) { 735 | var apiClient = this.getApiClient(serverId); 736 | if (apiClient) { 737 | if ("string" == typeof msg.Data) try { 738 | msg.Data = JSON.parse(msg.Data) 739 | } catch (err) {} 740 | apiClient.handleMessageReceived(msg) 741 | } 742 | } 743 | }, ConnectionManager 744 | }); 745 | --------------------------------------------------------------------------------