1(function () { 2 "use strict"; 3 4 window.DSC = window.DSC || {}; 5 const root = window.DSC.Core = window.DSC.Core || window.DSC_CORE || {}; 6 window.DSC_CORE = root; 7 8 function getContext() { 9 return root.getContext?.() || root.context || {}; 10 } 11 12 function log(message, data) { 13 root.log?.debug?.("ACCOUNT", message, data); 14 } 15 16 let unsubscribeAuthState = null; 17 let authReady = false; 18 19 function getAuth() { 20 try { 21 return root.firebase?.authService || root.firebase?.auth?.() || null; 22 } catch (error) { 23 root.log?.warn?.("ACCOUNT", "Firebase Auth unavailable", error); 24 return null; 25 } 26 } 27 28 function getGoogleProvider() { 29 const sdk = root.firebase?.sdk || window.firebase || null; 30 const Provider = sdk?.auth?.GoogleAuthProvider; 31 return typeof Provider === "function" ? new Provider() : null; 32 } 33 34 function trimText(value) { 35 return typeof value === "string" ? value.trim() : ""; 36 } 37 38 function normalizeEmail(email) { 39 return trimText(email).toLowerCase(); 40 } 41 42 function getSearchParams() { 43 try { 44 return new URLSearchParams(window.location.search); 45 } catch { 46 return new URLSearchParams(); 47 } 48 } 49 50 function hasSearchValue(name, expectedValue = null) { 51 const value = getSearchParams().get(name); 52 if (expectedValue === null) return trimText(value) !== ""; 53 return value === expectedValue; 54 } 55 56 function isSyncLoginPage() { 57 const pathname = String(window.location.pathname || "").toLocaleLowerCase("it-IT"); 58 return pathname.endsWith("/sync-login.html") || pathname === "/sync-login.html"; 59 } 60 61 function isCloudAccountAccessAllowed() { 62 // Formazione can restore an existing account; DSC+ still requires membership. 63 const formationPage = ["/formazione/", "/formazione/index.html"].includes(window.location.pathname); 64 return formationPage || hasSearchValue("dscplus", "beta") || 65 hasSearchValue("invite") || 66 hasSearchValue("admin", "true") || 67 hasSearchValue("controlcenter", "true") || 68 isSyncLoginPage(); 69 } 70 71 function getCloudAccountAccessDisabledMessage() { 72 return "Accesso DSC+ disponibile solo dal link beta o da un invito DSC+."; 73 } 74 75 function createAccessDisabledError() { 76 const error = new Error(getCloudAccountAccessDisabledMessage()); 77 error.code = "auth/cloud-account-access-disabled"; 78 return error; 79 } 80 81 function requireCloudAccountAccess() { 82 if (!isCloudAccountAccessAllowed()) { 83 throw createAccessDisabledError(); 84 } 85 } 86 87 function getProvider(user) { 88 const providers = Array.isArray(user?.providerData) 89 ? user.providerData.map(provider => provider?.providerId).filter(Boolean) 90 : []; 91 return providers[0] || "password"; 92 } 93 94 function normalizeUser(user) { 95 if (!user) return null; 96 97 return { 98 uid: user.uid || null, 99 email: user.email || null, 100 emailVerified: user.emailVerified === true, 101 displayName: user.displayName || null, 102 provider: getProvider(user), 103 status: "authenticated" 104 }; 105 } 106 107 function getAnonymousAccountState(context = getContext()) { 108 return { 109 authenticated: false, 110 identity: null, 111 email: null, 112 emailVerified: false, 113 displayName: null, 114 provider: null, 115 status: authReady ? "anonymous" : "loading" 116 }; 117 } 118 119 async function updateAuthContext(user) { 120 const identity = normalizeUser(user); 121 if (typeof root.updateContext !== "function") return identity; 122 const previousUid = trimText(getContext()?.identity?.uid); 123 const nextUid = trimText(identity?.uid); 124 const scopeChanged = previousUid !== nextUid; 125 const scopedContext = scopeChanged 126 ? root.repositoryManager?.switchIdentityScope?.(nextUid || null) 127 : null; 128 const nextContext = { 129 ...(scopeChanged && scopedContext ? scopedContext : getContext()), 130 authenticated: identity !== null, 131 identity, 132 membership: null, 133 membershipLoading: identity !== null, 134 features: [] 135 }; 136 if (scopeChanged && typeof root.setContext === "function") root.setContext(nextContext); 137 else root.updateContext(nextContext); 138 139 if (typeof root.membership?.syncMembershipForIdentity === "function") { 140 await root.membership.syncMembershipForIdentity(identity); 141 } 142 143 root.events?.emit?.("account:changed", service.getAccountState()); 144 return identity; 145 } 146 147 function createAuthError(error, fallback) { 148 const code = error?.code || ""; 149 const messages = {
150 "auth/email-already-in-use": "Questa email risulta gia registrata. Prova ad accedere oppure usa il recupero password.", 151 "auth/invalid-email": "L'indirizzo email non e valido.", 152 "auth/missing-email": "Inserisci l'indirizzo email.", 153 "auth/user-disabled": "Questo account e stato disabilitato.", 154 "auth/user-not-found": "Non esiste nessun account con questa email.", 155 "auth/wrong-password": "La password non e corretta.", 156 "auth/invalid-credential": "Email o password non corretti.", 157 "auth/invalid-login-credentials": "Email o password non corretti.", 158 "auth/weak-password": "La password e troppo debole. Usa almeno 6 caratteri.", 159 "auth/missing-password": "Inserisci la password.", 160 "auth/network-request-failed": "Connessione assente o instabile. Riprova quando sei online.", 161 "auth/too-many-requests": "Troppi tentativi ravvicinati. Attendi qualche minuto e riprova.", 162 "auth/operation-not-allowed": "Accesso email/password non abilitato nella configurazione Firebase.", 163 "auth/requires-recent-login": "Per questa operazione serve un accesso recente. Esci e accedi di nuovo.", 164 "auth/user-token-expired": "La sessione e scaduta. Accedi di nuovo.", 165 "auth/account-exists-with-different-credential": "Esiste gia un account con questa email usando un metodo di accesso diverso.", 166 "auth/cancelled-popup-request": "Operazione annullata.", 167 "auth/popup-blocked": "Il popup di accesso e stato bloccato dal browser.", 168 "auth/popup-closed-by-user": "Operazione annullata." 169 }; 170 171 const message = messages[code] || fallback || "Si e verificato un errore Firebase. Riprova piu tardi."; 172 const authError = new Error(message); 173 authError.code = code || "auth/unknown"; 174 authError.originalError = error || null; 175 return authError; 176 } 177 178 async function requireAuth() { 179 const auth = getAuth(); 180 if (!auth) { 181 throw createAuthError(null, "Autenticazione Firebase non disponibile."); 182 } 183 return auth; 184 } 185 186 async function requireCurrentUser() { 187 const auth = await requireAuth(); 188 const user = auth.currentUser || null; 189 if (!user) { 190 throw createAuthError({ code: "auth/user-not-found" }, "Devi accedere prima di eseguire questa operazione."); 191 } 192 return user; 193 } 194 195 function getSafeInsightsReturnTo() { 196 try { 197 const current = new URL(window.location.href); 198 if (current.pathname !== "/" && current.pathname !== "/index.html") return null; 199 const requested = current.searchParams.get("returnTo"); 200 if (!requested) return null; 201 const target = new URL(requested, current.origin); 202 return target.origin === current.origin && 203 target.pathname === "/admin/insights.html" && 204 target.search === "" && 205 target.hash === "" 206 ? target.pathname 207 : null; 208 } catch { 209 return null; 210 } 211 } 212 213 function returnToInsightsAfterAuthentication() { 214 const target = getSafeInsightsReturnTo(); 215 if (!target) return false; 216 if (typeof window.location.assign === "function") window.location.assign(target); 217 else window.location.href = target; 218 return true; 219 } 220 221 async function handleAuthState(user) { 222 authReady = true; 223 try { 224 if (user && !isCloudAccountAccessAllowed()) { 225 try { 226 await getAuth()?.signOut?.(); 227 } catch (signOutError) { 228 root.log?.warn?.("ACCOUNT", "blocked auth sign out failed", signOutError); 229 } 230 await updateAuthContext(null); 231 log("auth state blocked outside DSC+ access URL"); 232 return; 233 } 234 235 await updateAuthContext(user || null); 236 if (user) returnToInsightsAfterAuthentication(); 237 log("auth state changed", normalizeUser(user)); 238 } catch (error) { 239 root.log?.warn?.("ACCOUNT", "auth context update failed", error); 240 } 241 } 242 243 async function handleAuthStateError(error) { 244 authReady = true; 245 root.log?.warn?.("ACCOUNT", "auth state listener failed", error); 246 try { 247 await updateAuthContext(null); 248 } catch (updateError) { 249 root.log?.warn?.("ACCOUNT", "auth context reset failed", updateError); 250 } 251 } 252 253 const service = { 254 init() { 255 const auth = getAuth(); 256 if (!auth || typeof auth.onAuthStateChanged !== "function") { 257 authReady = true; 258 log("auth unavailable"); 259 return this; 260 } 261 262 if (!unsubscribeAuthState && root.firebaseAuthReady?.subscribe) { 263 unsubscribeAuthState = root.firebaseAuthReady.subscribe(authState => { 264 if (authState?.status === "AUTH_LOADING") return; 265 if (authState?.status === "AUTH_ERROR") { 266 void handleAuthStateError(authState.error); 267 return; 268 } 269 void handleAuthState(authState?.user || null); 270 }); 271 root.firebaseAuthReady.start?.(); 272 } else if (!unsubscribeAuthState) { 273 unsubscribeAuthState = auth.onAuthStateChanged(handleAuthState, handleAuthStateError); 274 } 275 276 log("service ready"); 277 return this; 278 }, 279 280 isAuthAvailable() { 281 return getAuth() !== null; 282 }, 283 284 isAuthReady() { 285 return authReady === true; 286 }, 287 288 isAuthenticated() { 289 return getContext().authenticated === true; 290 }, 291 292 isCloudAccountAccessAllowed, 293 294 getCloudAccountAccessDisabledMessage, 295 296 getSafeInsightsReturnTo, 297 298 getAccountState() { 299 const context = getContext();
300 const identity = context.identity || null; 301 const account = identity ? { 302 authenticated: context.authenticated === true, 303 identity, 304 email: identity.email || null, 305 emailVerified: identity.emailVerified === true, 306 displayName: identity.displayName || null, 307 provider: identity.provider || null, 308 status: context.membershipLoading === true ? "loading" : (context.authenticated === true ? "authenticated" : "anonymous"), 309 plan: context.membership?.plan || null 310 } : getAnonymousAccountState(context); 311 312 return { 313 ...account, 314 authenticated: context.authenticated === true, 315 pilotProfile: context.pilotProfile || null, 316 operator: context.operator || null, 317 workspace: context.workspace || null, 318 membership: context.membership || null, 319 features: Array.isArray(context.features) ? [...context.features] : [] 320 }; 321 }, 322 323 async registerWithEmail(email, password, options = {}) { 324 requireCloudAccountAccess(); 325 const auth = await requireAuth(); 326 const normalizedEmail = normalizeEmail(email); 327 const displayName = trimText(options.displayName); 328 329 if (!normalizedEmail) { 330 throw createAuthError({ code: "auth/invalid-email" }); 331 } 332 333 try { 334 const credential = await auth.createUserWithEmailAndPassword(normalizedEmail, password); 335 const user = credential?.user || auth.currentUser; 336 337 if (user && displayName && typeof user.updateProfile === "function") { 338 await user.updateProfile({ displayName }); 339 } 340 341 if (user && typeof user.sendEmailVerification === "function") { 342 await user.sendEmailVerification(); 343 } 344 345 if (user && typeof user.reload === "function") { 346 await user.reload(); 347 } 348 349 await updateAuthContext(auth.currentUser || user || null); 350 return this.getAccountState(); 351 } catch (error) { 352 throw createAuthError(error, "Registrazione non riuscita."); 353 } 354 }, 355 356 async loginWithEmail(email, password) { 357 requireCloudAccountAccess(); 358 const auth = await requireAuth(); 359 const normalizedEmail = normalizeEmail(email); 360 361 if (!normalizedEmail) { 362 throw createAuthError({ code: "auth/invalid-email" }); 363 } 364 365 try { 366 await auth.signInWithEmailAndPassword(normalizedEmail, password); 367 await updateAuthContext(auth.currentUser || null); 368 return this.getAccountState(); 369 } catch (error) { 370 throw createAuthError(error, "Accesso non riuscito."); 371 } 372 }, 373 374 async loginWithGoogle() { 375 requireCloudAccountAccess(); 376 const auth = await requireAuth(); 377 const provider = getGoogleProvider(); 378 379 if (!provider || typeof auth.signInWithPopup !== "function") { 380 throw createAuthError(null, "Accesso Google non disponibile nella configurazione Firebase."); 381 } 382 383 try { 384 await auth.signInWithPopup(provider); 385 await updateAuthContext(auth.currentUser || null); 386 return this.getAccountState(); 387 } catch (error) { 388 throw createAuthError(error, "Accesso Google non riuscito."); 389 } 390 }, 391 392 async logout() { 393 const auth = await requireAuth(); 394 395 try { 396 await auth.signOut(); 397 await updateAuthContext(null); 398 return this.getAccountState(); 399 } catch (error) { 400 throw createAuthError(error, "Logout non riuscito."); 401 } 402 }, 403 404 async sendPasswordReset(email) { 405 requireCloudAccountAccess(); 406 const auth = await requireAuth(); 407 const normalizedEmail = normalizeEmail(email); 408 409 if (!normalizedEmail) { 410 throw createAuthError({ code: "auth/invalid-email" }); 411 } 412 413 try { 414 await auth.sendPasswordResetEmail(normalizedEmail); 415 return true; 416 } catch (error) { 417 throw createAuthError(error, "Invio del recupero password non riuscito."); 418 } 419 }, 420 421 async sendEmailVerification() { 422 const user = await requireCurrentUser(); 423 424 try { 425 await user.sendEmailVerification(); 426 return true; 427 } catch (error) { 428 throw createAuthError(error, "Invio della verifica email non riuscito."); 429 } 430 }, 431 432 async refreshAuthState() { 433 const auth = await requireAuth(); 434 const user = auth.currentUser || null; 435
436 try { 437 if (user && typeof user.reload === "function") { 438 await user.reload(); 439 } 440 await updateAuthContext(auth.currentUser || null); 441 return this.getAccountState(); 442 } catch (error) { 443 throw createAuthError(error, "Aggiornamento dello stato account non riuscito."); 444 } 445 } 446 }; 447 448 root.account = service; 449})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.