IPPEnrollAndEntitleManager.sys.mjs (6372B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 IPPStartupCache: 9 "moz-src:///browser/components/ipprotection/IPPStartupCache.sys.mjs", 10 IPProtectionService: 11 "moz-src:///browser/components/ipprotection/IPProtectionService.sys.mjs", 12 IPPSignInWatcher: 13 "moz-src:///browser/components/ipprotection/IPPSignInWatcher.sys.mjs", 14 }); 15 16 const LOG_PREF = "browser.ipProtection.log"; 17 18 ChromeUtils.defineLazyGetter(lazy, "logConsole", function () { 19 return console.createInstance({ 20 prefix: "IPPEnrollAndEntitleManager", 21 maxLogLevel: Services.prefs.getBoolPref(LOG_PREF, false) ? "Debug" : "Warn", 22 }); 23 }); 24 25 /** 26 * This class manages the enrolling and entitlement. 27 */ 28 class IPPEnrollAndEntitleManagerSingleton extends EventTarget { 29 #runningPromise = null; 30 31 #entitlement = null; 32 33 constructor() { 34 super(); 35 36 this.handleEvent = this.#handleEvent.bind(this); 37 } 38 39 init() { 40 // We will use data from the cache until we are fully functional. Then we 41 // will recompute the state in `initOnStartupCompleted`. 42 this.#entitlement = lazy.IPPStartupCache.entitlement; 43 44 lazy.IPPSignInWatcher.addEventListener( 45 "IPPSignInWatcher:StateChanged", 46 this.handleEvent 47 ); 48 } 49 50 initOnStartupCompleted() { 51 if (!lazy.IPPSignInWatcher.isSignedIn) { 52 return; 53 } 54 55 try { 56 // This bit must be async because we want to trigger the updateState at 57 // the end of the rest of the initialization. 58 lazy.IPProtectionService.guardian 59 .isLinkedToGuardian(/* only cache: */ true) 60 .then( 61 async isLinked => { 62 if (isLinked) { 63 const { status, entitlement } = 64 await lazy.IPProtectionService.guardian.fetchUserInfo(); 65 if (status === 200) { 66 this.#setEntitlement(entitlement); 67 return; 68 } 69 } 70 this.#setEntitlement(null); 71 }, 72 () => { 73 // In case we were using cached values, it's time to reset them. 74 this.#setEntitlement(null); 75 } 76 ); 77 } catch (_) { 78 // In case we were using cached values, it's time to reset them. 79 this.#setEntitlement(null); 80 } 81 } 82 83 uninit() { 84 lazy.IPPSignInWatcher.removeEventListener( 85 "IPPSignInWatcher:StateChanged", 86 this.handleEvent 87 ); 88 89 this.#entitlement = null; 90 } 91 92 #handleEvent(_event) { 93 if (!lazy.IPPSignInWatcher.isSignedIn) { 94 this.#setEntitlement(null); 95 return; 96 } 97 98 this.maybeEnrollAndEntitle(); 99 } 100 101 maybeEnrollAndEntitle(forceRefetch = false) { 102 if (this.#runningPromise) { 103 return this.#runningPromise; 104 } 105 106 if (this.#entitlement && !forceRefetch) { 107 return Promise.resolve({ isEnrolledAndEntitled: true }); 108 } 109 110 const enrollAndEntitle = async () => { 111 const data = 112 await IPPEnrollAndEntitleManagerSingleton.#maybeEnrollAndEntitle(); 113 if (!data.entitlement) { 114 // Unset the entitlement if not available. 115 this.#setEntitlement(null); 116 return { isEnrolledAndEntitled: false, error: data.error }; 117 } 118 119 this.#setEntitlement(data.entitlement); 120 return { isEnrolledAndEntitled: true }; 121 }; 122 123 this.#runningPromise = enrollAndEntitle().finally(() => { 124 this.#runningPromise = null; 125 }); 126 127 return this.#runningPromise; 128 } 129 130 // This method is static because we don't want to change the internal state 131 // of the singleton. 132 static async #maybeEnrollAndEntitle() { 133 let isLinked = false; 134 try { 135 isLinked = await lazy.IPProtectionService.guardian.isLinkedToGuardian( 136 /* only cache: */ false 137 ); 138 } catch (error) { 139 // If not linked, it's not an issue. 140 } 141 142 if (isLinked) { 143 // Linked does not mean enrolled: it could be that the link comes from a 144 // previous MozillaVPN subscription. Let's see if `fetchUserInfo` is able 145 // to obtain the entitlement. 146 const { status, entitlement } = 147 await lazy.IPProtectionService.guardian.fetchUserInfo(); 148 if (status === 200) { 149 return { entitlement }; 150 } 151 } 152 153 try { 154 const enrollment = await lazy.IPProtectionService.guardian.enroll(); 155 if (!enrollment?.ok) { 156 return { entitlement: null, error: enrollment?.error }; 157 } 158 } catch (error) { 159 return { enrollment: null, error: error?.message }; 160 } 161 162 const { status, entitlement, error } = 163 await lazy.IPProtectionService.guardian.fetchUserInfo(); 164 lazy.logConsole.debug("Entitlement:", { status, entitlement, error }); 165 166 // If we see an error during the READY state, let's trigger an error state. 167 if (error || !entitlement || status != 200) { 168 return { entitlement: null, error: error || `Status: ${status}` }; 169 } 170 171 return { entitlement }; 172 } 173 174 #setEntitlement(entitlement) { 175 this.#entitlement = entitlement; 176 lazy.IPPStartupCache.storeEntitlement(this.#entitlement); 177 178 lazy.IPProtectionService.updateState(); 179 180 this.dispatchEvent( 181 new CustomEvent("IPPEnrollAndEntitleManager:StateChanged", { 182 bubbles: true, 183 composed: true, 184 }) 185 ); 186 } 187 188 get isEnrolledAndEntitled() { 189 return !!this.#entitlement; 190 } 191 192 /** 193 * Checks if a user has upgraded. 194 * 195 * @returns {boolean} 196 */ 197 get hasUpgraded() { 198 return this.#entitlement?.subscribed; 199 } 200 201 /** 202 * Checks if the entitlement exists and it contains a UUID 203 */ 204 get hasEntitlementUid() { 205 return !!this.#entitlement?.uid; 206 } 207 208 /** 209 * Checks if we have the entitlement 210 */ 211 get hasEntitlement() { 212 return !!this.#entitlement; 213 } 214 215 /** 216 * Checks if we're running the Alpha variant based on 217 * available features 218 */ 219 get isAlpha() { 220 return ( 221 !this.#entitlement?.autostart && 222 !this.#entitlement?.website_inclusion && 223 !this.#entitlement?.location_controls 224 ); 225 } 226 227 async refetchEntitlement() { 228 await this.maybeEnrollAndEntitle(true); 229 } 230 231 resetEntitlement() { 232 this.#setEntitlement(null); 233 } 234 } 235 236 const IPPEnrollAndEntitleManager = new IPPEnrollAndEntitleManagerSingleton(); 237 238 export { IPPEnrollAndEntitleManager };